From e4885a2fb2b0b1fa340f4c5af076184f86481e46 Mon Sep 17 00:00:00 2001 From: InSync Date: Thu, 12 Dec 2024 07:58:06 +0700 Subject: [PATCH] [red-knot] Understand `typing.Tuple` (#14927) Co-authored-by: Alex Waygood --- .../resources/mdtest/subscript/tuple.md | 52 +++++++++++++++++++ crates/red_knot_python_semantic/src/types.rs | 10 +++- .../src/types/infer.rs | 1 + .../red_knot_python_semantic/src/types/mro.rs | 5 +- 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md b/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md index 5582fa8a920c4..0affbc5ed57c4 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md +++ b/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md @@ -72,3 +72,55 @@ def _(m: int, n: int): # TODO: Support overloads... Should be `tuple[Literal[1, 'a', b"b"] | None, ...]` reveal_type(tuple_slice) # revealed: @Todo(return type) ``` + +## Inheritance + +```toml +[environment] +target-version = "3.9" +``` + +```py +# TODO: +# * `tuple.__class_getitem__` is always bound on 3.9 (`sys.version_info`) +# * `tuple[int, str]` is a valid base (generics) +# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[tuple]` is possibly unbound" +# error: [invalid-base] "Invalid class base with type `GenericAlias` (all bases must be a class, `Any`, `Unknown` or `Todo`)" +class A(tuple[int, str]): ... + +# Runtime value: `(A, tuple, object)` +# TODO: Generics +reveal_type(A.__mro__) # revealed: tuple[Literal[A], Unknown, Literal[object]] +``` + +## `typing.Tuple` + +### Correspondence with `tuple` + +`typing.Tuple` can be used interchangeably with `tuple`: + +```py +from typing import Tuple + +class A: ... + +def _(c: Tuple, d: Tuple[int, A], e: Tuple[Any, ...]): + reveal_type(c) # revealed: tuple + reveal_type(d) # revealed: tuple[int, A] + reveal_type(e) # revealed: @Todo(full tuple[...] support) +``` + +### Inheritance + +Inheriting from `Tuple` results in a MRO with `builtins.tuple` and `typing.Generic`. `Tuple` itself +is not a class. + +```py +from typing import Tuple + +class C(Tuple): ... + +# Runtime value: `(C, tuple, typing.Generic, object)` +# TODO: Add `Generic` to the MRO +reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[tuple], Unknown, Literal[object]] +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 2663b360a6e1c..0109bb1a3041f 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1758,6 +1758,7 @@ impl<'db> Type<'db> { Type::ClassLiteral(_) | Type::SubclassOf(_) => self.to_instance(db), // We treat `typing.Type` exactly the same as `builtins.type`: Type::KnownInstance(KnownInstanceType::Type) => KnownClass::Type.to_instance(db), + Type::KnownInstance(KnownInstanceType::Tuple) => KnownClass::Tuple.to_instance(db), Type::Union(union) => union.map(db, |element| element.in_type_expression(db)), Type::Unknown => Type::Unknown, // TODO map this to a new `Type::TypeVar` variant @@ -2137,6 +2138,8 @@ pub enum KnownInstanceType<'db> { Never, /// The symbol `typing.Any` (which can also be found as `typing_extensions.Any`) Any, + /// The symbol `typing.Tuple` (which can also be found as `typing_extensions.Tuple`) + Tuple, /// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`) Type, /// A single instance of `typing.TypeVar` @@ -2157,6 +2160,7 @@ impl<'db> KnownInstanceType<'db> { Self::NoReturn => "NoReturn", Self::Never => "Never", Self::Any => "Any", + Self::Tuple => "Tuple", Self::Type => "Type", Self::TypeAliasType(_) => "TypeAliasType", } @@ -2173,6 +2177,7 @@ impl<'db> KnownInstanceType<'db> { | Self::NoReturn | Self::Never | Self::Any + | Self::Tuple | Self::Type | Self::TypeAliasType(_) => Truthiness::AlwaysTrue, } @@ -2188,6 +2193,7 @@ impl<'db> KnownInstanceType<'db> { Self::NoReturn => "typing.NoReturn", Self::Never => "typing.Never", Self::Any => "typing.Any", + Self::Tuple => "typing.Tuple", Self::Type => "typing.Type", Self::TypeVar(typevar) => typevar.name(db), Self::TypeAliasType(_) => "typing.TypeAliasType", @@ -2204,7 +2210,8 @@ impl<'db> KnownInstanceType<'db> { Self::NoReturn => KnownClass::SpecialForm, Self::Never => KnownClass::SpecialForm, Self::Any => KnownClass::Object, - Self::Type => KnownClass::Object, + Self::Tuple => KnownClass::SpecialForm, + Self::Type => KnownClass::SpecialForm, Self::TypeVar(_) => KnownClass::TypeVar, Self::TypeAliasType(_) => KnownClass::TypeAliasType, } @@ -2231,6 +2238,7 @@ impl<'db> KnownInstanceType<'db> { ("typing" | "typing_extensions", "Union") => Some(Self::Union), ("typing" | "typing_extensions", "NoReturn") => Some(Self::NoReturn), ("typing" | "typing_extensions", "Never") => Some(Self::Never), + ("typing" | "typing_extensions", "Tuple") => Some(Self::Tuple), ("typing" | "typing_extensions", "Type") => Some(Self::Type), _ => None, } diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 2410aca8a5f84..432cc6de873b5 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -4862,6 +4862,7 @@ impl<'db> TypeInferenceBuilder<'db> { Type::Unknown } KnownInstanceType::Type => self.infer_subclass_of_type_expression(parameters), + KnownInstanceType::Tuple => self.infer_tuple_type_expression(parameters), KnownInstanceType::Any => Type::Any, } } diff --git a/crates/red_knot_python_semantic/src/types/mro.rs b/crates/red_knot_python_semantic/src/types/mro.rs index 9ee5b41113cf3..0514925248234 100644 --- a/crates/red_knot_python_semantic/src/types/mro.rs +++ b/crates/red_knot_python_semantic/src/types/mro.rs @@ -364,10 +364,13 @@ impl<'db> ClassBase<'db> { | KnownInstanceType::Never | KnownInstanceType::Optional => None, KnownInstanceType::Any => Some(Self::Any), - // TODO: classes inheriting from `typing.Type` also have `Generic` in their MRO + // TODO: Classes inheriting from `typing.Type` et al. also have `Generic` in their MRO KnownInstanceType::Type => { ClassBase::try_from_ty(db, KnownClass::Type.to_class_literal(db)) } + KnownInstanceType::Tuple => { + ClassBase::try_from_ty(db, KnownClass::Tuple.to_class_literal(db)) + } }, } }