diff --git a/crates/red_knot_python_semantic/resources/mdtest/generics/classes.md b/crates/red_knot_python_semantic/resources/mdtest/generics/classes.md index d6f78f1fed..cc410a43d3 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/generics/classes.md +++ b/crates/red_knot_python_semantic/resources/mdtest/generics/classes.md @@ -13,8 +13,6 @@ class C[T]: ... A class that inherits from a generic class, and fills its type parameters with typevars, is generic: ```py -# TODO: no error -# error: [non-subscriptable] class D[U](C[U]): ... ``` @@ -22,8 +20,6 @@ A class that inherits from a generic class, but fills its type parameters with c _not_ generic: ```py -# TODO: no error -# error: [non-subscriptable] class E(C[int]): ... ``` @@ -65,9 +61,7 @@ The type parameter can be specified explicitly: class C[T]: x: T -# TODO: no error # TODO: revealed: C[int] -# error: [non-subscriptable] reveal_type(C[int]()) # revealed: C ``` @@ -131,16 +125,11 @@ propagate through: class Base[T]: x: T | None = None -# TODO: no error -# error: [non-subscriptable] class Sub[U](Base[U]): ... -# TODO: no error # TODO: revealed: int | None -# error: [non-subscriptable] reveal_type(Base[int].x) # revealed: T | None # TODO: revealed: int | None -# error: [non-subscriptable] reveal_type(Sub[int].x) # revealed: T | None ``` @@ -155,8 +144,6 @@ Here, `Sub` is not a generic class, since it fills its superclass's type paramet ```pyi class Base[T]: ... -# TODO: no error -# error: [non-subscriptable] class Sub(Base[Sub]): ... reveal_type(Sub) # revealed: Literal[Sub] @@ -169,8 +156,6 @@ A similar case can work in a non-stub file, if forward references are stringifie ```py class Base[T]: ... -# TODO: no error -# error: [non-subscriptable] class Sub(Base["Sub"]): ... reveal_type(Sub) # revealed: Literal[Sub] @@ -183,8 +168,6 @@ In a non-stub file, without stringified forward references, this raises a `NameE ```py class Base[T]: ... -# TODO: the unresolved-reference error is correct, the non-subscriptable is not -# error: [non-subscriptable] # error: [unresolved-reference] class Sub(Base[Sub]): ... ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/class.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/class.md index e5d4956db9..bd97179738 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/class.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/class.md @@ -8,8 +8,6 @@ In type stubs, classes can reference themselves in their base class definitions. ```pyi class Foo[T]: ... -# TODO: actually is subscriptable -# error: [non-subscriptable] class Bar(Foo[Bar]): ... reveal_type(Bar) # revealed: Literal[Bar] diff --git a/crates/red_knot_python_semantic/src/types/class.rs b/crates/red_knot_python_semantic/src/types/class.rs index e9599d21b2..38ed876b96 100644 --- a/crates/red_knot_python_semantic/src/types/class.rs +++ b/crates/red_knot_python_semantic/src/types/class.rs @@ -39,7 +39,7 @@ pub struct Class<'db> { #[return_ref] pub(crate) name: ast::name::Name, - generic_context: Option>, + pub(crate) generic_context: Option>, body_scope: ScopeId<'db>, pub(crate) known: Option, diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 79604362a5..cb4e6cd3de 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -64,6 +64,7 @@ use crate::symbol::{ typing_extensions_symbol, Boundness, LookupError, }; use crate::types::call::{Argument, Bindings, CallArgumentTypes, CallArguments, CallError}; +use crate::types::class::{ClassLiteralType, MetaclassErrorKind}; use crate::types::diagnostic::{ report_implicit_return_type, report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable, report_invalid_assignment, @@ -80,12 +81,11 @@ use crate::types::generics::GenericContext; use crate::types::mro::MroErrorKind; use crate::types::unpacker::{UnpackResult, Unpacker}; use crate::types::{ - class::MetaclassErrorKind, todo_type, Class, DynamicType, FunctionType, InstanceType, - IntersectionBuilder, IntersectionType, KnownClass, KnownFunction, KnownInstanceType, - MetaclassCandidate, Parameter, ParameterForm, Parameters, SliceLiteralType, SubclassOfType, - Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, - TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, - UnionType, + todo_type, Class, DynamicType, FunctionType, InstanceType, IntersectionBuilder, + IntersectionType, KnownClass, KnownFunction, KnownInstanceType, MetaclassCandidate, Parameter, + ParameterForm, Parameters, SliceLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, + Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, + TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType, }; use crate::types::{CallableType, GeneralCallableType, Signature}; use crate::unpack::Unpack; @@ -5795,9 +5795,16 @@ impl<'db> TypeInferenceBuilder<'db> { } } - if matches!(value_ty, Type::ClassLiteral(class_literal) if class_literal.class().is_known(self.db(), KnownClass::Type)) - { - return KnownClass::GenericAlias.to_instance(self.db()); + if let Type::ClassLiteral(ClassLiteralType { class }) = value_ty { + if class.is_known(self.db(), KnownClass::Type) { + return KnownClass::GenericAlias.to_instance(self.db()); + } + + if class.generic_context(self.db()).is_some() { + // TODO: specialize the generic class using these explicit type + // variable assignments + return value_ty; + } } report_non_subscriptable( @@ -5820,6 +5827,10 @@ impl<'db> TypeInferenceBuilder<'db> { // TODO: proper support for generic classes // For now, just infer `Sequence`, if we see something like `Sequence[str]`. This allows us // to look up attributes on generic base classes, even if we don't understand generics yet. + // Note that this isn't handled by the clause up above for generic classes + // that use legacy type variables and an explicit `Generic` base class. + // Once we handle legacy typevars, this special case will be removed in + // favor of the specialization logic above. value_ty } _ => Type::unknown(),