mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
[ty] Understand homogeneous tuple annotations (#17998)
This commit is contained in:
parent
f301931159
commit
55df9271ba
17 changed files with 196 additions and 104 deletions
|
@ -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)
|
||||
```
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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, ...]
|
||||
|
||||
reveal_type(f) # revealed: @Todo(PEP 646)
|
||||
reveal_type(g) # revealed: @Todo(PEP 646)
|
||||
|
||||
# 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(i) # revealed: tuple[str | int, str | int]
|
||||
reveal_type(j) # revealed: tuple[str | int]
|
||||
```
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
||||
|
@ -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)
|
||||
```
|
||||
|
||||
|
|
|
@ -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`:
|
||||
|
|
|
@ -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, ...]`
|
||||
reveal_type(x + y) # revealed: tuple[int | Unknown, ...]
|
||||
reveal_type(x + (1, 2)) # revealed: tuple[int, ...]
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
```
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]))
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
@ -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, ...]))
|
||||
```
|
||||
|
||||
# TODO: should pass
|
||||
static_assert(is_subtype_of(tuple[int], tuple[object, ...])) # error: [static-assert-error]
|
||||
## 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]))
|
||||
|
||||
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
|
||||
|
|
|
@ -1273,17 +1273,8 @@ impl<'db> Type<'db> {
|
|||
)
|
||||
}
|
||||
|
||||
// Other than the special tuple-to-tuple case handled, above,
|
||||
// tuple subtyping delegates to `Instance(tuple)` in the same way as the literal types.
|
||||
//
|
||||
// All heterogeneous tuple types are subtypes of `Instance(<tuple>)`:
|
||||
// `Instance(<some class T>)` expresses "the set of all possible instances of the class `T`";
|
||||
// consequently, `Instance(<tuple>)` expresses "the set of all possible instances of the class `tuple`".
|
||||
// This type can be spelled in type annotations as `tuple[object, ...]` (since `tuple` is covariant).
|
||||
//
|
||||
// Note that this is not the same type as the type spelled in type annotations as `tuple`;
|
||||
// as that type is equivalent to `type[Any, ...]` (and therefore not a fully static type).
|
||||
(Type::Tuple(_), _) => KnownClass::Tuple.to_instance(db).is_subtype_of(db, target),
|
||||
// `tuple[A, B, C]` is a subtype of `tuple[A | B | C, ...]`
|
||||
(Type::Tuple(tuple), _) => tuple.homogeneous_supertype(db).is_subtype_of(db, target),
|
||||
|
||||
(Type::BoundSuper(_), Type::BoundSuper(_)) => self.is_equivalent_to(db, target),
|
||||
(Type::BoundSuper(_), _) => KnownClass::Super.to_instance(db).is_subtype_of(db, target),
|
||||
|
@ -1494,10 +1485,10 @@ impl<'db> Type<'db> {
|
|||
// This special case is required because the left-hand side tuple might be a
|
||||
// gradual type, so we can not rely on subtyping. This allows us to assign e.g.
|
||||
// `tuple[Any, int]` to `tuple`.
|
||||
(Type::Tuple(_), _)
|
||||
if KnownClass::Tuple
|
||||
.to_instance(db)
|
||||
.is_assignable_to(db, target) =>
|
||||
//
|
||||
// `tuple[A, B, C]` is assignable to `tuple[A | B | C, ...]`
|
||||
(Type::Tuple(tuple), _)
|
||||
if tuple.homogeneous_supertype(db).is_assignable_to(db, target) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
|
@ -1960,9 +1951,9 @@ impl<'db> Type<'db> {
|
|||
!known_instance.is_instance_of(db, instance.class())
|
||||
}
|
||||
|
||||
(known_instance_ty @ Type::KnownInstance(_), Type::Tuple(_))
|
||||
| (Type::Tuple(_), known_instance_ty @ Type::KnownInstance(_)) => {
|
||||
known_instance_ty.is_disjoint_from(db, KnownClass::Tuple.to_instance(db))
|
||||
(known_instance_ty @ Type::KnownInstance(_), Type::Tuple(tuple))
|
||||
| (Type::Tuple(tuple), known_instance_ty @ Type::KnownInstance(_)) => {
|
||||
known_instance_ty.is_disjoint_from(db, tuple.homogeneous_supertype(db))
|
||||
}
|
||||
|
||||
(Type::BooleanLiteral(..), Type::NominalInstance(instance))
|
||||
|
@ -2088,17 +2079,9 @@ impl<'db> Type<'db> {
|
|||
.any(|(e1, e2)| e1.is_disjoint_from(db, *e2))
|
||||
}
|
||||
|
||||
(Type::Tuple(..), instance @ Type::NominalInstance(_))
|
||||
| (instance @ Type::NominalInstance(_), Type::Tuple(..)) => {
|
||||
// We cannot be sure if the tuple is disjoint from the instance because:
|
||||
// - 'other' might be the homogeneous arbitrary-length tuple type
|
||||
// tuple[T, ...] (which we don't have support for yet); if all of
|
||||
// our element types are not disjoint with T, this is not disjoint
|
||||
// - 'other' might be a user subtype of tuple, which, if generic
|
||||
// over the same or compatible *Ts, would overlap with tuple.
|
||||
//
|
||||
// TODO: add checks for the above cases once we support them
|
||||
instance.is_disjoint_from(db, KnownClass::Tuple.to_instance(db))
|
||||
(Type::Tuple(tuple), instance @ Type::NominalInstance(_))
|
||||
| (instance @ Type::NominalInstance(_), Type::Tuple(tuple)) => {
|
||||
instance.is_disjoint_from(db, tuple.homogeneous_supertype(db))
|
||||
}
|
||||
|
||||
(Type::PropertyInstance(_), other) | (other, Type::PropertyInstance(_)) => {
|
||||
|
@ -2588,7 +2571,7 @@ impl<'db> Type<'db> {
|
|||
KnownClass::Str.to_instance(db).instance_member(db, name)
|
||||
}
|
||||
Type::BytesLiteral(_) => KnownClass::Bytes.to_instance(db).instance_member(db, name),
|
||||
Type::Tuple(_) => KnownClass::Tuple.to_instance(db).instance_member(db, name),
|
||||
Type::Tuple(tuple) => tuple.homogeneous_supertype(db).instance_member(db, name),
|
||||
|
||||
Type::AlwaysTruthy | Type::AlwaysFalsy => Type::object(db).instance_member(db, name),
|
||||
Type::ModuleLiteral(_) => KnownClass::ModuleType
|
||||
|
@ -3573,8 +3556,10 @@ impl<'db> Type<'db> {
|
|||
db,
|
||||
[
|
||||
KnownClass::Str.to_instance(db),
|
||||
// TODO: tuple[str, ...]
|
||||
KnownClass::Tuple.to_instance(db),
|
||||
KnownClass::Tuple.to_specialized_instance(
|
||||
db,
|
||||
[KnownClass::Str.to_instance(db)],
|
||||
),
|
||||
],
|
||||
)),
|
||||
Parameter::positional_only(Some(Name::new_static("start")))
|
||||
|
@ -3854,6 +3839,9 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
Some(KnownClass::Type) => {
|
||||
let str_instance = KnownClass::Str.to_instance(db);
|
||||
let type_instance = KnownClass::Type.to_instance(db);
|
||||
|
||||
// ```py
|
||||
// class type:
|
||||
// @overload
|
||||
|
@ -3869,20 +3857,26 @@ impl<'db> Type<'db> {
|
|||
Name::new_static("o"),
|
||||
))
|
||||
.with_annotated_type(Type::any())]),
|
||||
Some(KnownClass::Type.to_instance(db)),
|
||||
Some(type_instance),
|
||||
),
|
||||
Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("name")))
|
||||
.with_annotated_type(KnownClass::Str.to_instance(db)),
|
||||
.with_annotated_type(str_instance),
|
||||
Parameter::positional_only(Some(Name::new_static("bases")))
|
||||
// TODO: Should be tuple[type, ...] once we have support for homogenous tuples
|
||||
.with_annotated_type(KnownClass::Tuple.to_instance(db)),
|
||||
.with_annotated_type(
|
||||
KnownClass::Tuple
|
||||
.to_specialized_instance(db, [type_instance]),
|
||||
),
|
||||
Parameter::positional_only(Some(Name::new_static("dict")))
|
||||
// TODO: Should be `dict[str, Any]` once we have support for generics
|
||||
.with_annotated_type(KnownClass::Dict.to_instance(db)),
|
||||
.with_annotated_type(
|
||||
KnownClass::Dict.to_specialized_instance(
|
||||
db,
|
||||
[str_instance, Type::any()],
|
||||
),
|
||||
),
|
||||
]),
|
||||
Some(KnownClass::Type.to_instance(db)),
|
||||
Some(type_instance),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -7924,6 +7918,11 @@ pub struct TupleType<'db> {
|
|||
}
|
||||
|
||||
impl<'db> TupleType<'db> {
|
||||
fn homogeneous_supertype(self, db: &'db dyn Db) -> Type<'db> {
|
||||
KnownClass::Tuple
|
||||
.to_specialized_instance(db, [UnionType::from_elements(db, self.elements(db))])
|
||||
}
|
||||
|
||||
pub(crate) fn from_elements<T: Into<Type<'db>>>(
|
||||
db: &'db dyn Db,
|
||||
types: impl IntoIterator<Item = T>,
|
||||
|
|
|
@ -2107,9 +2107,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
definition: Definition<'db>,
|
||||
) {
|
||||
if let Some(annotation) = parameter.annotation() {
|
||||
let _annotated_ty = self.file_expression_type(annotation);
|
||||
// TODO `tuple[annotated_type, ...]`
|
||||
let ty = KnownClass::Tuple.to_instance(self.db());
|
||||
let ty = if annotation.is_starred_expr() {
|
||||
todo_type!("PEP 646")
|
||||
} else {
|
||||
let annotated_type = self.file_expression_type(annotation);
|
||||
KnownClass::Tuple.to_specialized_instance(self.db(), [annotated_type])
|
||||
};
|
||||
|
||||
self.add_declaration_with_binding(
|
||||
parameter.into(),
|
||||
definition,
|
||||
|
@ -2119,8 +2123,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
self.add_binding(
|
||||
parameter.into(),
|
||||
definition,
|
||||
// TODO `tuple[Unknown, ...]`
|
||||
KnownClass::Tuple.to_instance(self.db()),
|
||||
KnownClass::Tuple.to_specialized_instance(self.db(), [Type::unknown()]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2473,16 +2476,32 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
except_handler_definition: &ExceptHandlerDefinitionKind,
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
fn extract_tuple_specialization<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Type<'db>> {
|
||||
let class = ty.into_nominal_instance()?.class();
|
||||
if !class.is_known(db, KnownClass::Tuple) {
|
||||
return None;
|
||||
}
|
||||
let ClassType::Generic(class) = class else {
|
||||
return None;
|
||||
};
|
||||
let specialization = class.specialization(db).types(db)[0];
|
||||
let specialization_instance = specialization.to_instance(db)?;
|
||||
|
||||
specialization_instance
|
||||
.is_assignable_to(db, KnownClass::BaseException.to_instance(db))
|
||||
.then_some(specialization_instance)
|
||||
}
|
||||
|
||||
let node = except_handler_definition.handled_exceptions();
|
||||
|
||||
// If there is no handled exception, it's invalid syntax;
|
||||
// a diagnostic will have already been emitted
|
||||
let node_ty = node.map_or(Type::unknown(), |ty| self.infer_expression(ty));
|
||||
let type_base_exception = KnownClass::BaseException.to_subclass_of(self.db());
|
||||
|
||||
// If it's an `except*` handler, this won't actually be the type of the bound symbol;
|
||||
// it will actually be the type of the generic parameters to `BaseExceptionGroup` or `ExceptionGroup`.
|
||||
let symbol_ty = if let Type::Tuple(tuple) = node_ty {
|
||||
let type_base_exception = KnownClass::BaseException.to_subclass_of(self.db());
|
||||
let mut builder = UnionBuilder::new(self.db());
|
||||
for element in tuple.elements(self.db()).iter().copied() {
|
||||
builder = builder.add(
|
||||
|
@ -2500,27 +2519,37 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
);
|
||||
}
|
||||
builder.build()
|
||||
} else if node_ty.is_subtype_of(self.db(), KnownClass::Tuple.to_instance(self.db())) {
|
||||
todo_type!("Homogeneous tuple in exception handler")
|
||||
} else if node_ty.is_assignable_to(self.db(), type_base_exception) {
|
||||
node_ty.to_instance(self.db()).expect(
|
||||
"`Type::to_instance()` should always return `Some()` \
|
||||
if called on a type assignable to `type[BaseException]`",
|
||||
)
|
||||
} else if node_ty.is_assignable_to(
|
||||
self.db(),
|
||||
KnownClass::Tuple.to_specialized_instance(self.db(), [type_base_exception]),
|
||||
) {
|
||||
extract_tuple_specialization(self.db(), node_ty)
|
||||
.unwrap_or_else(|| KnownClass::BaseException.to_instance(self.db()))
|
||||
} else if node_ty.is_assignable_to(
|
||||
self.db(),
|
||||
UnionType::from_elements(
|
||||
self.db(),
|
||||
[
|
||||
type_base_exception,
|
||||
KnownClass::Tuple.to_specialized_instance(self.db(), [type_base_exception]),
|
||||
],
|
||||
),
|
||||
) {
|
||||
KnownClass::BaseException.to_instance(self.db())
|
||||
} else {
|
||||
let type_base_exception = KnownClass::BaseException.to_subclass_of(self.db());
|
||||
if node_ty.is_assignable_to(self.db(), type_base_exception) {
|
||||
node_ty.to_instance(self.db()).expect(
|
||||
"`Type::to_instance()` should always return `Some()` \
|
||||
if called on a type assignable to `type[BaseException]`",
|
||||
)
|
||||
} else {
|
||||
if let Some(node) = node {
|
||||
report_invalid_exception_caught(&self.context, node, node_ty);
|
||||
}
|
||||
Type::unknown()
|
||||
if let Some(node) = node {
|
||||
report_invalid_exception_caught(&self.context, node, node_ty);
|
||||
}
|
||||
Type::unknown()
|
||||
};
|
||||
|
||||
let symbol_ty = if except_handler_definition.is_star() {
|
||||
// TODO: we should infer `ExceptionGroup` if `node_ty` is a subtype of `tuple[type[Exception], ...]`
|
||||
// (needs support for homogeneous tuples).
|
||||
//
|
||||
// TODO: should be generic with `symbol_ty` as the generic parameter
|
||||
KnownClass::BaseExceptionGroup.to_instance(self.db())
|
||||
} else {
|
||||
|
@ -7970,7 +7999,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
}
|
||||
|
||||
match element {
|
||||
ast::Expr::EllipsisLiteral(_) | ast::Expr::Starred(_) => true,
|
||||
ast::Expr::Starred(_) => true,
|
||||
ast::Expr::Subscript(ast::ExprSubscript { value, .. }) => {
|
||||
let value_ty = if builder.deferred_state.in_string_annotation() {
|
||||
// Using `.expression_type` does not work in string annotations, because
|
||||
|
@ -7980,17 +8009,23 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
builder.expression_type(value)
|
||||
};
|
||||
|
||||
matches!(value_ty, Type::KnownInstance(KnownInstanceType::Unpack))
|
||||
value_ty == Type::KnownInstance(KnownInstanceType::Unpack)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// - homogeneous tuples
|
||||
// - PEP 646
|
||||
// TODO: PEP 646
|
||||
match tuple_slice {
|
||||
ast::Expr::Tuple(elements) => {
|
||||
if let [element, ellipsis @ ast::Expr::EllipsisLiteral(_)] = &*elements.elts {
|
||||
self.infer_expression(ellipsis);
|
||||
let result = KnownClass::Tuple
|
||||
.to_specialized_instance(self.db(), [self.infer_type_expression(element)]);
|
||||
self.store_expression_type(tuple_slice, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
let mut element_types = Vec::with_capacity(elements.len());
|
||||
|
||||
// Whether to infer `Todo` for the whole tuple
|
||||
|
@ -8005,7 +8040,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
}
|
||||
|
||||
let ty = if return_todo {
|
||||
todo_type!("full tuple[...] support")
|
||||
todo_type!("PEP 646")
|
||||
} else {
|
||||
TupleType::from_elements(self.db(), element_types)
|
||||
};
|
||||
|
@ -8021,7 +8056,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
let single_element_ty = self.infer_type_expression(single_element);
|
||||
if element_could_alter_type_of_whole_tuple(single_element, single_element_ty, self)
|
||||
{
|
||||
todo_type!("full tuple[...] support")
|
||||
todo_type!("PEP 646")
|
||||
} else {
|
||||
TupleType::from_elements(self.db(), std::iter::once(single_element_ty))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue