Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ from typing_extensions import TypeVarTuple
Ts = TypeVarTuple("Ts")

def append_int(*args: *Ts) -> tuple[*Ts, int]:
# TODO: tuple[*Ts]
reveal_type(args) # revealed: tuple[Unknown, ...]
reveal_type(args) # revealed: @Todo(PEP 646)

return (*args, 1)

# TODO should be tuple[Literal[True], Literal["a"], int]
reveal_type(append_int(True, "a")) # revealed: @Todo(full tuple[...] support)
reveal_type(append_int(True, "a")) # revealed: @Todo(PEP 646)
```
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,13 @@ R_co = TypeVar("R_co", covariant=True)
Alias: TypeAlias = int

def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
# TODO: should understand the annotation
reveal_type(args) # revealed: tuple[Unknown, ...]

reveal_type(args) # revealed: tuple[@Todo(`Unpack[]` special form), ...]
reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`)

def g() -> TypeGuard[int]: ...
def h() -> TypeIs[int]: ...
def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.kwargs) -> R_co:
# TODO: should understand the annotation
reveal_type(args) # revealed: tuple[Unknown, ...]
reveal_type(args) # revealed: tuple[@Todo(Support for `typing.ParamSpec`), ...]
reveal_type(kwargs) # revealed: dict[str, @Todo(Support for `typing.ParamSpec`)]
return callback(42, *args, **kwargs)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,12 @@ reveal_type(a) # revealed: tuple[()]
reveal_type(b) # revealed: tuple[int]
reveal_type(c) # revealed: tuple[str, int]
reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]]
reveal_type(e) # revealed: tuple[str, ...]

# TODO: homogeneous tuples, PEP-646 tuples, generics
reveal_type(e) # revealed: @Todo(full tuple[...] support)
reveal_type(f) # revealed: @Todo(full tuple[...] support)
reveal_type(g) # revealed: @Todo(full tuple[...] support)
reveal_type(h) # revealed: tuple[list[int], list[int]]
reveal_type(f) # revealed: @Todo(PEP 646)
reveal_type(g) # revealed: @Todo(PEP 646)

reveal_type(h) # revealed: tuple[list[int], list[int]]
reveal_type(i) # revealed: tuple[str | int, str | int]
reveal_type(j) # revealed: tuple[str | int]
```
Expand Down
4 changes: 2 additions & 2 deletions crates/ty_python_semantic/resources/mdtest/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1676,7 +1676,7 @@ functions are instances of that class:
```py
def f(): ...

reveal_type(f.__defaults__) # revealed: @Todo(full tuple[...] support) | None
reveal_type(f.__defaults__) # revealed: tuple[Any, ...] | None
reveal_type(f.__kwdefaults__) # revealed: dict[str, Any] | None
```

Expand Down Expand Up @@ -1730,7 +1730,7 @@ All attribute access on literal `bytes` types is currently delegated to `builtin
```py
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[@Todo(Support for `typing.TypeAlias`)], /) -> bytes
reveal_type(b"foo".join)
# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`), start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool
# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`) | tuple[@Todo(Support for `typing.TypeAlias`), ...], start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool
reveal_type(b"foo".endswith)
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,7 @@ reveal_type(A() + b"foo") # revealed: A
reveal_type(b"foo" + A()) # revealed: bytes

reveal_type(A() + ()) # revealed: A
# TODO this should be `A`, since `tuple.__add__` doesn't support `A` instances
reveal_type(() + A()) # revealed: @Todo(full tuple[...] support)
reveal_type(() + A()) # revealed: A

literal_string_instance = "foo" * 1_000_000_000
# the test is not testing what it's meant to be testing if this isn't a `LiteralString`:
Expand Down
5 changes: 3 additions & 2 deletions crates/ty_python_semantic/resources/mdtest/binary/tuples.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def _(x: tuple[int, str], y: tuple[None, tuple[int]]):

```py
def _(x: tuple[int, ...], y: tuple[str, ...]):
reveal_type(x + y) # revealed: @Todo(full tuple[...] support)
reveal_type(x + (1, 2)) # revealed: @Todo(full tuple[...] support)
# TODO: should be `tuple[int | str, ...]`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any insight into what we are missing here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not really... there's so many generics fixes happening at once, though, that I feel like I'm sort-of losing track 😄

Copy link
Contributor

@carljm carljm May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fine, not a blocker here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like @dcreager's fixing it over in #18021 😁

reveal_type(x + y) # revealed: tuple[int | Unknown, ...]
reveal_type(x + (1, 2)) # revealed: tuple[int, ...]
```
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type(b"Foo", (), {})
# error: [no-matching-overload] "No overload of class `type` matches arguments"
type("Foo", Base, {})

# TODO: this should be an error
# error: [no-matching-overload] "No overload of class `type` matches arguments"
type("Foo", (1, 2), {})

# TODO: this should be an error
Expand Down
13 changes: 9 additions & 4 deletions crates/ty_python_semantic/resources/mdtest/exception/basic.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def foo(
x: type[AttributeError],
y: tuple[type[OSError], type[RuntimeError]],
z: tuple[type[BaseException], ...],
zz: tuple[type[TypeError | RuntimeError], ...],
zzz: type[BaseException] | tuple[type[BaseException], ...],
):
try:
help()
Expand All @@ -53,8 +55,11 @@ def foo(
except y as f:
reveal_type(f) # revealed: OSError | RuntimeError
except z as g:
# TODO: should be `BaseException`
reveal_type(g) # revealed: @Todo(full tuple[...] support)
reveal_type(g) # revealed: BaseException
except zz as h:
reveal_type(h) # revealed: TypeError | RuntimeError
except zzz as i:
reveal_type(i) # revealed: BaseException
```

## Invalid exception handlers
Expand Down Expand Up @@ -86,9 +91,9 @@ def foo(
# error: [invalid-exception-caught]
except y as f:
reveal_type(f) # revealed: OSError | RuntimeError | Unknown
# error: [invalid-exception-caught]
except z as g:
# TODO: should emit a diagnostic here:
reveal_type(g) # revealed: @Todo(full tuple[...] support)
reveal_type(g) # revealed: Unknown
```

## Object raised is not an exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ def f(a, b: int, c=1, d: int = 2, /, e=3, f: Literal[4] = 4, *args: object, g=5,
reveal_type(f) # revealed: Literal[4]
reveal_type(g) # revealed: Unknown | Literal[5]
reveal_type(h) # revealed: Literal[6]
# TODO: should be `tuple[object, ...]`
reveal_type(args) # revealed: tuple[Unknown, ...]
reveal_type(args) # revealed: tuple[object, ...]
reveal_type(kwargs) # revealed: dict[str, str]
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,7 @@ y: MyIntOrStr = None

```py
type ListOrSet[T] = list[T] | set[T]

# TODO: Should be `tuple[typing.TypeVar | typing.ParamSpec | typing.TypeVarTuple, ...]`,
# as specified in the `typeshed` stubs.
reveal_type(ListOrSet.__type_params__) # revealed: @Todo(full tuple[...] support)
reveal_type(ListOrSet.__type_params__) # revealed: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
```

## `TypeAliasType` properties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def _(m: int, n: int):

tuple_slice = t[m:n]
# TODO: Should be `tuple[Literal[1, 'a', b"b"] | None, ...]`
reveal_type(tuple_slice) # revealed: @Todo(full tuple[...] support)
reveal_type(tuple_slice) # revealed: tuple[Unknown, ...]
```

## Inheritance
Expand Down Expand Up @@ -101,7 +101,7 @@ class A: ...
def _(c: Tuple, d: Tuple[int, A], e: Tuple[Any, ...]):
reveal_type(c) # revealed: tuple[Unknown, ...]
reveal_type(d) # revealed: tuple[int, A]
reveal_type(e) # revealed: @Todo(full tuple[...] support)
reveal_type(e) # revealed: tuple[Any, ...]
```

### Inheritance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ static_assert(is_assignable_to(Meta, type[Any]))
static_assert(is_assignable_to(Meta, type[Unknown]))
```

## Tuple types
## Heterogeneous tuple types

```py
from ty_extensions import static_assert, is_assignable_to, AlwaysTruthy, AlwaysFalsy
Expand Down Expand Up @@ -232,6 +232,35 @@ static_assert(not is_assignable_to(tuple[int, int], tuple[Literal[1], int]))
static_assert(not is_assignable_to(tuple[Any, Literal[2]], tuple[int, str]))
```

## Assignability of heterogeneous tuple types to homogeneous tuple types

While a homogeneous tuple type is not assignable to any heterogeneous tuple types, a heterogeneous
tuple type can be assignable to a homogeneous tuple type, and homogeneous tuple types can be
assignable to `Sequence`:

```py
from typing import Literal, Any, Sequence
from ty_extensions import static_assert, is_assignable_to, Not, AlwaysFalsy

static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Literal[1, 2], ...]))
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[int, ...]))
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[int | str, ...]))
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Any, ...]))
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Not[AlwaysFalsy], ...]))
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], Sequence[int]))
static_assert(is_assignable_to(tuple[int, ...], Sequence[int]))
static_assert(is_assignable_to(tuple[int, ...], Sequence[Any]))
static_assert(is_assignable_to(tuple[Any, ...], Sequence[int]))

static_assert(is_assignable_to(tuple[()], tuple[Literal[1, 2], ...]))
static_assert(is_assignable_to(tuple[()], tuple[int, ...]))
static_assert(is_assignable_to(tuple[()], tuple[int | str, ...]))
static_assert(is_assignable_to(tuple[()], tuple[Not[AlwaysFalsy], ...]))
static_assert(is_assignable_to(tuple[()], Sequence[int]))

static_assert(not is_assignable_to(tuple[int, int], tuple[str, ...]))
```

## Union types

```py
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ static_assert(is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[Literal[1]])
static_assert(is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[Literal[1], Literal[3]]))

static_assert(not is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[Literal[1], int]))
static_assert(not is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[int, ...]))

# TODO: should pass
static_assert(is_disjoint_from(tuple[int, int], tuple[None, ...])) # error: [static-assert-error]
```

## Unions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ static_assert(not is_fully_static(Any | str))
static_assert(not is_fully_static(str | Unknown))
static_assert(not is_fully_static(Intersection[Any, Not[LiteralString]]))

static_assert(not is_fully_static(tuple[Any, ...]))
# TODO: should pass
static_assert(not is_fully_static(tuple[Any, ...])) # error: [static-assert-error]

static_assert(not is_fully_static(tuple[int, Any]))
static_assert(not is_fully_static(type[Any]))
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ static_assert(is_subtype_of(Literal[b"foo"], bytes))
static_assert(is_subtype_of(Literal[b"foo"], object))
```

## Tuple types
## Heterogeneous tuple types

```py
from ty_extensions import is_subtype_of, static_assert
Expand Down Expand Up @@ -150,9 +150,36 @@ static_assert(not is_subtype_of(tuple[B1, B2], tuple[Unrelated, Unrelated]))
static_assert(not is_subtype_of(tuple[B1, B2], tuple[()]))
static_assert(not is_subtype_of(tuple[B1, B2], tuple[A1]))
static_assert(not is_subtype_of(tuple[B1, B2], tuple[A1, A2, Unrelated]))
static_assert(is_subtype_of(tuple[int], tuple[object, ...]))
```

## Subtyping of heterogeneous tuple types and homogeneous tuple types

While a homogeneous tuple type is not a subtype of any heterogeneous tuple types, a heterogeneous
tuple type can be a subtype of a homogeneous tuple type, and homogeneous tuple types can be subtypes
of `Sequence`:

```py
from typing import Literal, Any, Sequence
from ty_extensions import static_assert, is_subtype_of, Not, AlwaysFalsy

static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Literal[1, 2], ...]))
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[int, ...]))
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[int | str, ...]))
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Not[AlwaysFalsy], ...]))
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], Sequence[int]))
static_assert(is_subtype_of(tuple[int, ...], Sequence[int]))

static_assert(is_subtype_of(tuple[()], tuple[Literal[1, 2], ...]))
static_assert(is_subtype_of(tuple[()], tuple[int, ...]))
static_assert(is_subtype_of(tuple[()], tuple[int | str, ...]))
static_assert(is_subtype_of(tuple[()], tuple[Not[AlwaysFalsy], ...]))
static_assert(is_subtype_of(tuple[()], Sequence[int]))

# TODO: should pass
static_assert(is_subtype_of(tuple[int], tuple[object, ...])) # error: [static-assert-error]
static_assert(not is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Any, ...]))
static_assert(not is_subtype_of(tuple[int, int], tuple[str, ...]))
static_assert(not is_subtype_of(tuple[int, ...], Sequence[Any]))
static_assert(not is_subtype_of(tuple[Any, ...], Sequence[int]))
```

## Union types
Expand Down
Loading
Loading