mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-02 09:52:18 +00:00
[red-knot] Allow type[]
to be subscripted (#13667)
Fixed a TODO by adding another TODO. It's the red-knot way!
## Summary
`builtins.type` can be subscripted at runtime on Python 3.9+, even
though it has no `__class_getitem__` method and its metaclass (which
is... itself) has no `__getitem__` method. The special case is
[hardcoded directly into `PyObject_GetItem` in
CPython](744caa8ef4/Objects/abstract.c (L181-L184)
).
We just have to replicate the special case in our semantic model.
This will fail at runtime on Python <3.9. However, there's a bunch of
outstanding questions (detailed in the TODO comment I added) regarding
how we deal with subscriptions of other generic types on lower Python
versions. Since we want to avoid too many false positives for now, I
haven't tried to address this; I've just made `type` subscriptable on
all Python versions.
## Test Plan
`cargo test -p red_knot_python_semantic --lib`
This commit is contained in:
parent
fb90f5a13d
commit
71b52b83e4
2 changed files with 19 additions and 9 deletions
|
@ -837,6 +837,7 @@ pub enum KnownClass {
|
|||
Set,
|
||||
Dict,
|
||||
// Types
|
||||
GenericAlias,
|
||||
ModuleType,
|
||||
FunctionType,
|
||||
// Typeshed
|
||||
|
@ -857,6 +858,7 @@ impl<'db> KnownClass {
|
|||
Self::Dict => "dict",
|
||||
Self::List => "list",
|
||||
Self::Type => "type",
|
||||
Self::GenericAlias => "GenericAlias",
|
||||
Self::ModuleType => "ModuleType",
|
||||
Self::FunctionType => "FunctionType",
|
||||
Self::NoneType => "NoneType",
|
||||
|
@ -880,7 +882,9 @@ impl<'db> KnownClass {
|
|||
| Self::Tuple
|
||||
| Self::Set
|
||||
| Self::Dict => builtins_symbol_ty(db, self.as_str()),
|
||||
Self::ModuleType | Self::FunctionType => types_symbol_ty(db, self.as_str()),
|
||||
Self::GenericAlias | Self::ModuleType | Self::FunctionType => {
|
||||
types_symbol_ty(db, self.as_str())
|
||||
}
|
||||
Self::NoneType => typeshed_symbol_ty(db, self.as_str()),
|
||||
}
|
||||
}
|
||||
|
@ -910,6 +914,7 @@ impl<'db> KnownClass {
|
|||
"set" => Some(Self::Set),
|
||||
"dict" => Some(Self::Dict),
|
||||
"list" => Some(Self::List),
|
||||
"GenericAlias" => Some(Self::GenericAlias),
|
||||
"NoneType" => Some(Self::NoneType),
|
||||
"ModuleType" => Some(Self::ModuleType),
|
||||
"FunctionType" => Some(Self::FunctionType),
|
||||
|
@ -934,7 +939,7 @@ impl<'db> KnownClass {
|
|||
| Self::Tuple
|
||||
| Self::Set
|
||||
| Self::Dict => module.name() == "builtins",
|
||||
Self::ModuleType | Self::FunctionType => module.name() == "types",
|
||||
Self::GenericAlias | Self::ModuleType | Self::FunctionType => module.name() == "types",
|
||||
Self::NoneType => matches!(module.name().as_str(), "_typeshed" | "types"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2828,6 +2828,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
|
||||
// Otherwise, if the value is itself a class and defines `__class_getitem__`,
|
||||
// return its return type.
|
||||
//
|
||||
// TODO: lots of classes are only subscriptable at runtime on Python 3.9+,
|
||||
// *but* we should also allow them to be subscripted in stubs
|
||||
// (and in annotations if `from __future__ import annotations` is enabled),
|
||||
// even if the target version is Python 3.8 or lower,
|
||||
// despite the fact that there will be no corresponding `__class_getitem__`
|
||||
// method in these `sys.version_info` branches.
|
||||
if value_ty.is_class(self.db) {
|
||||
let dunder_class_getitem_method = value_ty.member(self.db, "__class_getitem__");
|
||||
if !dunder_class_getitem_method.is_unbound() {
|
||||
|
@ -2848,6 +2855,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
});
|
||||
}
|
||||
|
||||
if matches!(value_ty, Type::Class(class) if class.is_known(self.db, KnownClass::Type))
|
||||
{
|
||||
return KnownClass::GenericAlias.to_instance(self.db);
|
||||
}
|
||||
|
||||
self.non_subscriptable_diagnostic(
|
||||
(&**value).into(),
|
||||
value_ty,
|
||||
|
@ -6194,13 +6206,6 @@ mod tests {
|
|||
)?;
|
||||
|
||||
let expected_diagnostics = &[
|
||||
// TODO: these `__class_getitem__` diagnostics are all false positives:
|
||||
// (`builtins.type` is unique at runtime
|
||||
// as it can be subscripted even though it has no `__class_getitem__` method)
|
||||
"Cannot subscript object of type `Literal[type]` with no `__class_getitem__` method",
|
||||
"Cannot subscript object of type `Literal[type]` with no `__class_getitem__` method",
|
||||
"Cannot subscript object of type `Literal[type]` with no `__class_getitem__` method",
|
||||
"Cannot subscript object of type `Literal[type]` with no `__class_getitem__` method",
|
||||
// Should be `AttributeError`:
|
||||
"Revealed type is `@Todo`",
|
||||
// Should be `OSError | RuntimeError`:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue