diff --git a/crates/red_knot_python_semantic/resources/mdtest/metaclass.md b/crates/red_knot_python_semantic/resources/mdtest/metaclass.md index f8f408789c..a8056d1903 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/metaclass.md +++ b/crates/red_knot_python_semantic/resources/mdtest/metaclass.md @@ -194,3 +194,26 @@ class A[T: str](metaclass=M): ... reveal_type(A.__class__) # revealed: Literal[M] ``` + +## Metaclasses of metaclasses + +```py +class Foo(type): ... +class Bar(type, metaclass=Foo): ... +class Baz(type, metaclass=Bar): ... +class Spam(metaclass=Baz): ... + +reveal_type(Spam.__class__) # revealed: Literal[Baz] +reveal_type(Spam.__class__.__class__) # revealed: Literal[Bar] +reveal_type(Spam.__class__.__class__.__class__) # revealed: Literal[Foo] + +def test(x: Spam): + reveal_type(x.__class__) # revealed: type[Spam] + reveal_type(x.__class__.__class__) # revealed: type[Baz] + reveal_type(x.__class__.__class__.__class__) # revealed: type[Bar] + reveal_type(x.__class__.__class__.__class__.__class__) # revealed: type[Foo] + reveal_type(x.__class__.__class__.__class__.__class__.__class__) # revealed: type[type] + + # revealed: type[type] + reveal_type(x.__class__.__class__.__class__.__class__.__class__.__class__.__class__.__class__) +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_of/any.md b/crates/red_knot_python_semantic/resources/mdtest/type_of/any.md index f8945d79b1..b04db11509 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_of/any.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_of/any.md @@ -1,4 +1,4 @@ -# type[Any] +# `type[Any]` ## Simple @@ -51,3 +51,22 @@ x: type[object] = type x: type[object] = A x: type[object] = A() # error: [invalid-assignment] ``` + +## The type of `Any` is `type[Any]` + +`Any` represents an unknown set of possible runtime values. If `x` is of type `Any`, the type of +`x.__class__` is also unknown and remains dynamic, *except* that we know it must be a class object +of some kind. As such, the type of `x.__class__` is `type[Any]` rather than `Any`: + +```py +from typing import Any +from does_not_exist import SomethingUnknown # error: [unresolved-import] + +reveal_type(SomethingUnknown) # revealed: Unknown + +def test(x: Any, y: SomethingUnknown): + reveal_type(x.__class__) # revealed: type[Any] + reveal_type(x.__class__.__class__.__class__.__class__) # revealed: type[Any] + reveal_type(y.__class__) # revealed: type[Unknown] + reveal_type(y.__class__.__class__.__class__.__class__) # revealed: type[Unknown] +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_of/typing_dot_Type.md b/crates/red_knot_python_semantic/resources/mdtest/type_of/typing_dot_Type.md index 415e964938..ff31bb1516 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_of/typing_dot_Type.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_of/typing_dot_Type.md @@ -9,7 +9,7 @@ from typing import Type class A: ... -def _(c: Type, d: Type[A], e: Type[A]): +def _(c: Type, d: Type[A]): reveal_type(c) # revealed: type reveal_type(d) # revealed: type[A] c = d # fine diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 390df3ee05..53048ef35d 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1844,23 +1844,22 @@ impl<'db> Type<'db> { Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db), Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db), Type::ClassLiteral(ClassLiteralType { class }) => class.metaclass(db), - Type::SubclassOf(SubclassOfType { - base: ClassBase::Class(class), - }) => Type::subclass_of( - class - .try_metaclass(db) - .ok() - .and_then(Type::into_class_literal) - .unwrap_or_else(|| KnownClass::Type.to_class_literal(db).expect_class_literal()) - .class, - ), - Type::SubclassOf(_) => Type::Any, + Type::SubclassOf(SubclassOfType { base }) => match base { + ClassBase::Any | ClassBase::Unknown | ClassBase::Todo(_) => *self, + ClassBase::Class(class) => Type::subclass_of_base( + ClassBase::try_from_ty(db, class.metaclass(db)).unwrap_or(ClassBase::Unknown), + ), + }, + Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class_literal(db), - Type::Any => Type::Any, - Type::Unknown => Type::Unknown, + Type::Any => Type::subclass_of_base(ClassBase::Any), + Type::Unknown => Type::subclass_of_base(ClassBase::Unknown), // TODO intersections - Type::Intersection(_) => todo_type!(), - todo @ Type::Todo(_) => *todo, + Type::Intersection(_) => Type::subclass_of_base( + ClassBase::try_from_ty(db, todo_type!("Intersection meta-type")) + .expect("Type::Todo should be a valid ClassBase"), + ), + Type::Todo(todo) => Type::subclass_of_base(ClassBase::Todo(*todo)), } } diff --git a/crates/red_knot_python_semantic/src/types/mro.rs b/crates/red_knot_python_semantic/src/types/mro.rs index bd9982f0a6..e8ca1caf1e 100644 --- a/crates/red_knot_python_semantic/src/types/mro.rs +++ b/crates/red_knot_python_semantic/src/types/mro.rs @@ -334,7 +334,7 @@ impl<'db> ClassBase<'db> { /// Attempt to resolve `ty` into a `ClassBase`. /// /// Return `None` if `ty` is not an acceptable type for a class base. - fn try_from_ty(db: &'db dyn Db, ty: Type<'db>) -> Option { + pub(super) fn try_from_ty(db: &'db dyn Db, ty: Type<'db>) -> Option { match ty { Type::Any => Some(Self::Any), Type::Unknown => Some(Self::Unknown), @@ -449,6 +449,12 @@ impl<'db> From> for Type<'db> { } } +impl<'db> From<&ClassBase<'db>> for Type<'db> { + fn from(value: &ClassBase<'db>) -> Self { + Self::from(*value) + } +} + /// Implementation of the [C3-merge algorithm] for calculating a Python class's /// [method resolution order]. ///