diff --git a/crates/ty_python_semantic/resources/mdtest/mro.md b/crates/ty_python_semantic/resources/mdtest/mro.md index 70369b5b05..3252178cf9 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 fceb6462c8..ec87630178 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 @@ -140,6 +140,43 @@ info[revealed-type]: Revealed type ``` +``` +error[duplicate-base]: Duplicate base class `Eggs` + --> src/mdtest_snippet.py:16:7 + | +14 | # error: [duplicate-base] "Duplicate base class `Spam`" +15 | # error: [duplicate-base] "Duplicate base class `Eggs`" +16 | class Ham( + | _______^ +17 | | Spam, +18 | | Eggs, +19 | | Bar, +20 | | Baz, +21 | | Spam, +22 | | Eggs, +23 | | ): ... + | |_^ +24 | +25 | # fmt: on + | +info: The definition of class `Ham` will raise `TypeError` at runtime + --> src/mdtest_snippet.py:18:5 + | +16 | class Ham( +17 | Spam, +18 | Eggs, + | ---- Class `Eggs` first included in bases list here +19 | Bar, +20 | Baz, +21 | Spam, +22 | Eggs, + | ^^^^ Class `Eggs` later repeated here +23 | ): ... + | +info: rule `duplicate-base` is enabled by default + +``` + ``` error[duplicate-base]: Duplicate base class `Spam` --> src/mdtest_snippet.py:16:7 @@ -178,43 +215,6 @@ info: rule `duplicate-base` is enabled by default ``` -``` -error[duplicate-base]: Duplicate base class `Eggs` - --> src/mdtest_snippet.py:16:7 - | -14 | # error: [duplicate-base] "Duplicate base class `Spam`" -15 | # error: [duplicate-base] "Duplicate base class `Eggs`" -16 | class Ham( - | _______^ -17 | | Spam, -18 | | Eggs, -19 | | Bar, -20 | | Baz, -21 | | Spam, -22 | | Eggs, -23 | | ): ... - | |_^ -24 | -25 | # fmt: on - | -info: The definition of class `Ham` will raise `TypeError` at runtime - --> src/mdtest_snippet.py:18:5 - | -16 | class Ham( -17 | Spam, -18 | Eggs, - | ---- Class `Eggs` first included in bases list here -19 | Bar, -20 | Baz, -21 | Spam, -22 | Eggs, - | ^^^^ Class `Eggs` later repeated here -23 | ): ... - | -info: rule `duplicate-base` is enabled by default - -``` - ``` info[revealed-type]: Revealed type --> src/mdtest_snippet.py:27:13 diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 9212781eab..cd48fa9877 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 2069ec257e..d11b124571 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 56bccc8b68..9eccbdb2c7 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)),