diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index ce84a55e6d..3e686b1636 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -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"), } } diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 349ef57651..f34892ff5d 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -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`: