diff --git a/crates/ty_python_semantic/resources/mdtest/mro.md b/crates/ty_python_semantic/resources/mdtest/mro.md index 70369b5b051fbb..3252178cf9017a 100644 --- a/crates/ty_python_semantic/resources/mdtest/mro.md +++ b/crates/ty_python_semantic/resources/mdtest/mro.md @@ -177,6 +177,23 @@ if not isinstance(DoesNotExist, type): reveal_type(Foo.__mro__) # revealed: tuple[, Unknown, ] ``` +## Inheritance from `type[Any]` and `type[Unknown]` + +Inheritance from `type[Any]` and `type[Unknown]` is also permitted, in keeping with the gradual +guarantee: + +```py +from typing import Any +from ty_extensions import Unknown, Intersection + +def f(x: type[Any], y: Intersection[Unknown, type[Any]]): + class Foo(x): ... + reveal_type(Foo.__mro__) # revealed: tuple[, Any, ] + + class Bar(y): ... + reveal_type(Bar.__mro__) # revealed: tuple[, Unknown, ] +``` + ## `__bases__` lists that cause errors at runtime If the class's `__bases__` cause an exception to be raised at runtime and therefore the class diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap index fceb6462c89ecd..ec876301783ea0 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Order_tests_-_`__bases__`_lists_with_duplicate_bases.snap @@ -141,7 +141,7 @@ info[revealed-type]: Revealed type ``` ``` -error[duplicate-base]: Duplicate base class `Spam` +error[duplicate-base]: Duplicate base class `Eggs` --> src/mdtest_snippet.py:16:7 | 14 | # error: [duplicate-base] "Duplicate base class `Spam`" @@ -160,18 +160,17 @@ error[duplicate-base]: Duplicate base class `Spam` 25 | # fmt: on | info: The definition of class `Ham` will raise `TypeError` at runtime - --> src/mdtest_snippet.py:17:5 + --> src/mdtest_snippet.py:18:5 | -15 | # error: [duplicate-base] "Duplicate base class `Eggs`" 16 | class Ham( 17 | Spam, - | ---- Class `Spam` first included in bases list here 18 | Eggs, + | ---- Class `Eggs` first included in bases list here 19 | Bar, 20 | Baz, 21 | Spam, - | ^^^^ Class `Spam` later repeated here 22 | Eggs, + | ^^^^ Class `Eggs` later repeated here 23 | ): ... | info: rule `duplicate-base` is enabled by default @@ -179,7 +178,7 @@ info: rule `duplicate-base` is enabled by default ``` ``` -error[duplicate-base]: Duplicate base class `Eggs` +error[duplicate-base]: Duplicate base class `Spam` --> src/mdtest_snippet.py:16:7 | 14 | # error: [duplicate-base] "Duplicate base class `Spam`" @@ -198,17 +197,18 @@ error[duplicate-base]: Duplicate base class `Eggs` 25 | # fmt: on | info: The definition of class `Ham` will raise `TypeError` at runtime - --> src/mdtest_snippet.py:18:5 + --> src/mdtest_snippet.py:17:5 | +15 | # error: [duplicate-base] "Duplicate base class `Eggs`" 16 | class Ham( 17 | Spam, + | ---- Class `Spam` first included in bases list here 18 | Eggs, - | ---- Class `Eggs` first included in bases list here 19 | Bar, 20 | Baz, 21 | Spam, + | ^^^^ Class `Spam` later repeated here 22 | Eggs, - | ^^^^ Class `Eggs` later repeated here 23 | ): ... | info: rule `duplicate-base` is enabled by default diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 9212781eab003d..cd48fa9877f6ae 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -543,13 +543,6 @@ impl<'db> Type<'db> { Self::Dynamic(DynamicType::Unknown) } - pub(crate) fn into_dynamic(self) -> Option { - match self { - Type::Dynamic(dynamic_type) => Some(dynamic_type), - _ => None, - } - } - pub fn object(db: &'db dyn Db) -> Self { KnownClass::Object.to_instance(db) } diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index 2069ec257ea1f5..d11b1245714cf3 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -107,16 +107,20 @@ impl<'db> ClassBase<'db> { { Self::try_from_type(db, todo_type!("GenericAlias instance")) } + Type::SubclassOf(subclass_of) => subclass_of + .subclass_of() + .into_dynamic() + .map(ClassBase::Dynamic), Type::Intersection(inter) => { - let dynamic_element = inter + let valid_element = inter .positive(db) .iter() - .find_map(|elem| elem.into_dynamic())?; + .find_map(|elem| ClassBase::try_from_type(db, *elem))?; if ty.is_disjoint_from(db, KnownClass::Type.to_instance(db)) { None } else { - Some(ClassBase::Dynamic(dynamic_element)) + Some(valid_element) } } Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs? @@ -137,7 +141,6 @@ impl<'db> ClassBase<'db> { | Type::LiteralString | Type::Tuple(_) | Type::ModuleLiteral(_) - | Type::SubclassOf(_) | Type::TypeVar(_) | Type::BoundSuper(_) | Type::ProtocolInstance(_) diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index 56bccc8b68806f..9eccbdb2c74b66 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -138,6 +138,13 @@ impl<'db> SubclassOfInner<'db> { } } + pub(crate) const fn into_dynamic(self) -> Option { + match self { + Self::Class(_) => None, + Self::Dynamic(dynamic) => Some(dynamic), + } + } + pub(crate) fn try_from_type(db: &'db dyn Db, ty: Type<'db>) -> Option { match ty { Type::Dynamic(dynamic) => Some(Self::Dynamic(dynamic)),