diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/starred.md b/crates/ty_python_semantic/resources/mdtest/annotations/starred.md index e102a79a2d..3bb3de68bf 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/starred.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/starred.md @@ -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) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md index c225220e01..3887d9bb84 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md @@ -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) diff --git a/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md b/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md index 3045460ec4..996dc31245 100644 --- a/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md +++ b/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md @@ -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] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 3c655db8e3..acd0799c28 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -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) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/binary/instances.md b/crates/ty_python_semantic/resources/mdtest/binary/instances.md index 465527a4c4..c27e70ed76 100644 --- a/crates/ty_python_semantic/resources/mdtest/binary/instances.md +++ b/crates/ty_python_semantic/resources/mdtest/binary/instances.md @@ -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`: diff --git a/crates/ty_python_semantic/resources/mdtest/binary/tuples.md b/crates/ty_python_semantic/resources/mdtest/binary/tuples.md index 49fb5b7333..ae23e4090a 100644 --- a/crates/ty_python_semantic/resources/mdtest/binary/tuples.md +++ b/crates/ty_python_semantic/resources/mdtest/binary/tuples.md @@ -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, ...] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/call/builtins.md b/crates/ty_python_semantic/resources/mdtest/call/builtins.md index c93afe9baa..a2bee55560 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/builtins.md +++ b/crates/ty_python_semantic/resources/mdtest/call/builtins.md @@ -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 diff --git a/crates/ty_python_semantic/resources/mdtest/exception/basic.md b/crates/ty_python_semantic/resources/mdtest/exception/basic.md index f313532303..e8befe07d8 100644 --- a/crates/ty_python_semantic/resources/mdtest/exception/basic.md +++ b/crates/ty_python_semantic/resources/mdtest/exception/basic.md @@ -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 diff --git a/crates/ty_python_semantic/resources/mdtest/function/parameters.md b/crates/ty_python_semantic/resources/mdtest/function/parameters.md index d61a320c62..eb7316fe91 100644 --- a/crates/ty_python_semantic/resources/mdtest/function/parameters.md +++ b/crates/ty_python_semantic/resources/mdtest/function/parameters.md @@ -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] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md b/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md index 6f84dc5481..c7b4820b29 100644 --- a/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md @@ -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 diff --git a/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md b/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md index 20d01d4fb8..fbd4434d02 100644 --- a/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md @@ -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 diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index 7f800567db..d70151109d 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -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 diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md index bc9d2b9c27..a5935725b6 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md @@ -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 diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_fully_static.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_fully_static.md index 3551742960..ef9f677c55 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_fully_static.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_fully_static.md @@ -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])) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md index 22fba74fe5..f9a97c8c51 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md @@ -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 diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index bf91743bb2..60418aac62 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -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()`: - // `Instance()` expresses "the set of all possible instances of the class `T`"; - // consequently, `Instance()` 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>>( db: &'db dyn Db, types: impl IntoIterator, diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 43c384c84f..8f3878a01d 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -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> { + 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)) }