Skip to content

Commit e4885a2

Browse files
[red-knot] Understand typing.Tuple (#14927)
Co-authored-by: Alex Waygood <[email protected]>
1 parent a7e5e42 commit e4885a2

File tree

4 files changed

+66
-2
lines changed

4 files changed

+66
-2
lines changed

crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md

+52
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,55 @@ def _(m: int, n: int):
7272
# TODO: Support overloads... Should be `tuple[Literal[1, 'a', b"b"] | None, ...]`
7373
reveal_type(tuple_slice) # revealed: @Todo(return type)
7474
```
75+
76+
## Inheritance
77+
78+
```toml
79+
[environment]
80+
target-version = "3.9"
81+
```
82+
83+
```py
84+
# TODO:
85+
# * `tuple.__class_getitem__` is always bound on 3.9 (`sys.version_info`)
86+
# * `tuple[int, str]` is a valid base (generics)
87+
# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[tuple]` is possibly unbound"
88+
# error: [invalid-base] "Invalid class base with type `GenericAlias` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
89+
class A(tuple[int, str]): ...
90+
91+
# Runtime value: `(A, tuple, object)`
92+
# TODO: Generics
93+
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Unknown, Literal[object]]
94+
```
95+
96+
## `typing.Tuple`
97+
98+
### Correspondence with `tuple`
99+
100+
`typing.Tuple` can be used interchangeably with `tuple`:
101+
102+
```py
103+
from typing import Tuple
104+
105+
class A: ...
106+
107+
def _(c: Tuple, d: Tuple[int, A], e: Tuple[Any, ...]):
108+
reveal_type(c) # revealed: tuple
109+
reveal_type(d) # revealed: tuple[int, A]
110+
reveal_type(e) # revealed: @Todo(full tuple[...] support)
111+
```
112+
113+
### Inheritance
114+
115+
Inheriting from `Tuple` results in a MRO with `builtins.tuple` and `typing.Generic`. `Tuple` itself
116+
is not a class.
117+
118+
```py
119+
from typing import Tuple
120+
121+
class C(Tuple): ...
122+
123+
# Runtime value: `(C, tuple, typing.Generic, object)`
124+
# TODO: Add `Generic` to the MRO
125+
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[tuple], Unknown, Literal[object]]
126+
```

crates/red_knot_python_semantic/src/types.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -1758,6 +1758,7 @@ impl<'db> Type<'db> {
17581758
Type::ClassLiteral(_) | Type::SubclassOf(_) => self.to_instance(db),
17591759
// We treat `typing.Type` exactly the same as `builtins.type`:
17601760
Type::KnownInstance(KnownInstanceType::Type) => KnownClass::Type.to_instance(db),
1761+
Type::KnownInstance(KnownInstanceType::Tuple) => KnownClass::Tuple.to_instance(db),
17611762
Type::Union(union) => union.map(db, |element| element.in_type_expression(db)),
17621763
Type::Unknown => Type::Unknown,
17631764
// TODO map this to a new `Type::TypeVar` variant
@@ -2137,6 +2138,8 @@ pub enum KnownInstanceType<'db> {
21372138
Never,
21382139
/// The symbol `typing.Any` (which can also be found as `typing_extensions.Any`)
21392140
Any,
2141+
/// The symbol `typing.Tuple` (which can also be found as `typing_extensions.Tuple`)
2142+
Tuple,
21402143
/// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`)
21412144
Type,
21422145
/// A single instance of `typing.TypeVar`
@@ -2157,6 +2160,7 @@ impl<'db> KnownInstanceType<'db> {
21572160
Self::NoReturn => "NoReturn",
21582161
Self::Never => "Never",
21592162
Self::Any => "Any",
2163+
Self::Tuple => "Tuple",
21602164
Self::Type => "Type",
21612165
Self::TypeAliasType(_) => "TypeAliasType",
21622166
}
@@ -2173,6 +2177,7 @@ impl<'db> KnownInstanceType<'db> {
21732177
| Self::NoReturn
21742178
| Self::Never
21752179
| Self::Any
2180+
| Self::Tuple
21762181
| Self::Type
21772182
| Self::TypeAliasType(_) => Truthiness::AlwaysTrue,
21782183
}
@@ -2188,6 +2193,7 @@ impl<'db> KnownInstanceType<'db> {
21882193
Self::NoReturn => "typing.NoReturn",
21892194
Self::Never => "typing.Never",
21902195
Self::Any => "typing.Any",
2196+
Self::Tuple => "typing.Tuple",
21912197
Self::Type => "typing.Type",
21922198
Self::TypeVar(typevar) => typevar.name(db),
21932199
Self::TypeAliasType(_) => "typing.TypeAliasType",
@@ -2204,7 +2210,8 @@ impl<'db> KnownInstanceType<'db> {
22042210
Self::NoReturn => KnownClass::SpecialForm,
22052211
Self::Never => KnownClass::SpecialForm,
22062212
Self::Any => KnownClass::Object,
2207-
Self::Type => KnownClass::Object,
2213+
Self::Tuple => KnownClass::SpecialForm,
2214+
Self::Type => KnownClass::SpecialForm,
22082215
Self::TypeVar(_) => KnownClass::TypeVar,
22092216
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
22102217
}
@@ -2231,6 +2238,7 @@ impl<'db> KnownInstanceType<'db> {
22312238
("typing" | "typing_extensions", "Union") => Some(Self::Union),
22322239
("typing" | "typing_extensions", "NoReturn") => Some(Self::NoReturn),
22332240
("typing" | "typing_extensions", "Never") => Some(Self::Never),
2241+
("typing" | "typing_extensions", "Tuple") => Some(Self::Tuple),
22342242
("typing" | "typing_extensions", "Type") => Some(Self::Type),
22352243
_ => None,
22362244
}

crates/red_knot_python_semantic/src/types/infer.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4862,6 +4862,7 @@ impl<'db> TypeInferenceBuilder<'db> {
48624862
Type::Unknown
48634863
}
48644864
KnownInstanceType::Type => self.infer_subclass_of_type_expression(parameters),
4865+
KnownInstanceType::Tuple => self.infer_tuple_type_expression(parameters),
48654866
KnownInstanceType::Any => Type::Any,
48664867
}
48674868
}

crates/red_knot_python_semantic/src/types/mro.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -364,10 +364,13 @@ impl<'db> ClassBase<'db> {
364364
| KnownInstanceType::Never
365365
| KnownInstanceType::Optional => None,
366366
KnownInstanceType::Any => Some(Self::Any),
367-
// TODO: classes inheriting from `typing.Type` also have `Generic` in their MRO
367+
// TODO: Classes inheriting from `typing.Type` et al. also have `Generic` in their MRO
368368
KnownInstanceType::Type => {
369369
ClassBase::try_from_ty(db, KnownClass::Type.to_class_literal(db))
370370
}
371+
KnownInstanceType::Tuple => {
372+
ClassBase::try_from_ty(db, KnownClass::Tuple.to_class_literal(db))
373+
}
371374
},
372375
}
373376
}

0 commit comments

Comments
 (0)