diff --git a/crates/ty_python_semantic/resources/mdtest/exception/basic.md b/crates/ty_python_semantic/resources/mdtest/exception/basic.md index e8befe07d8..6097625eda 100644 --- a/crates/ty_python_semantic/resources/mdtest/exception/basic.md +++ b/crates/ty_python_semantic/resources/mdtest/exception/basic.md @@ -62,6 +62,20 @@ def foo( reveal_type(i) # revealed: BaseException ``` +We do not emit an `invalid-exception-caught` if a class is caught that has `Any` or `Unknown` in its +MRO, as the dynamic element in the MRO could materialize to some subclass of `BaseException`: + +```py +from compat import BASE_EXCEPTION_CLASS # error: [unresolved-import] "Cannot resolve imported module `compat`" + +class Error(BASE_EXCEPTION_CLASS): ... + +try: + ... +except Error as err: + ... +``` + ## Invalid exception handlers ```py diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index a702f9b56c..597fd4a614 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -204,6 +204,34 @@ static_assert(is_assignable_to(type[AnyMeta], type[object])) static_assert(is_assignable_to(type[AnyMeta], type[Any])) ``` +## Class-literals that inherit from `Any` + +Class-literal types that inherit from `Any` are assignable to any type `T` where `T` is assignable +to `type`: + +```py +from typing import Any +from ty_extensions import is_assignable_to, static_assert, TypeOf + +def test(x: Any): + class Foo(x): ... + class Bar(Any): ... + static_assert(is_assignable_to(TypeOf[Foo], Any)) + static_assert(is_assignable_to(TypeOf[Foo], type)) + static_assert(is_assignable_to(TypeOf[Foo], type[int])) + static_assert(is_assignable_to(TypeOf[Foo], type[Any])) + + static_assert(is_assignable_to(TypeOf[Bar], Any)) + static_assert(is_assignable_to(TypeOf[Bar], type)) + static_assert(is_assignable_to(TypeOf[Bar], type[int])) + static_assert(is_assignable_to(TypeOf[Bar], type[Any])) + + static_assert(not is_assignable_to(TypeOf[Foo], int)) + static_assert(not is_assignable_to(TypeOf[Bar], int)) +``` + +This is because the `Any` element in the MRO could materialize to any subtype of `type`. + ## Heterogeneous tuple types ```py diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 2e65524a94..8c8ad9f461 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1542,6 +1542,14 @@ impl<'db> Type<'db> { true } + (Type::ClassLiteral(class), Type::SubclassOf(_)) + if class + .iter_mro(db, None) + .any(class_base::ClassBase::is_dynamic) => + { + true + } + // Every `type[...]` is assignable to `type` (Type::SubclassOf(_), _) if KnownClass::Type diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index cd0c273cb4..66add36f2c 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -303,6 +303,10 @@ impl<'db> ClassBase<'db> { } } } + + pub(crate) const fn is_dynamic(self) -> bool { + matches!(self, Self::Dynamic(_)) + } } impl<'db> From> for ClassBase<'db> {