Skip to content

Commit

Permalink
[red-knot] Understand typing.Tuple (#14927)
Browse files Browse the repository at this point in the history
Co-authored-by: Alex Waygood <[email protected]>
  • Loading branch information
InSyncWithFoo and AlexWaygood authored Dec 12, 2024
1 parent a7e5e42 commit e4885a2
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
```
10 changes: 9 additions & 1 deletion crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand All @@ -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",
}
Expand All @@ -2173,6 +2177,7 @@ impl<'db> KnownInstanceType<'db> {
| Self::NoReturn
| Self::Never
| Self::Any
| Self::Tuple
| Self::Type
| Self::TypeAliasType(_) => Truthiness::AlwaysTrue,
}
Expand All @@ -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",
Expand All @@ -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,
}
Expand All @@ -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,
}
Expand Down
1 change: 1 addition & 0 deletions crates/red_knot_python_semantic/src/types/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand Down
5 changes: 4 additions & 1 deletion crates/red_knot_python_semantic/src/types/mro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
},
}
}
Expand Down

0 comments on commit e4885a2

Please sign in to comment.