mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 10:48:32 +00:00
[ty] Allow a class to inherit from an intersection if the intersection contains a dynamic type and the intersection is not disjoint from type
(#18055)
This commit is contained in:
parent
c7b6108cb8
commit
41fa082414
3 changed files with 42 additions and 1 deletions
|
@ -154,6 +154,29 @@ reveal_type(E.__mro__) # revealed: tuple[<class 'E'>, <class 'B'>, <class 'C'>,
|
|||
reveal_type(F.__mro__)
|
||||
```
|
||||
|
||||
## Inheritance with intersections that include `Unknown`
|
||||
|
||||
An intersection that includes `Unknown` or `Any` is permitted as long as the intersection is not
|
||||
disjoint from `type`.
|
||||
|
||||
```py
|
||||
from does_not_exist import DoesNotExist # error: [unresolved-import]
|
||||
|
||||
reveal_type(DoesNotExist) # revealed: Unknown
|
||||
|
||||
if hasattr(DoesNotExist, "__mro__"):
|
||||
reveal_type(DoesNotExist) # revealed: Unknown & <Protocol with members '__mro__'>
|
||||
|
||||
class Foo(DoesNotExist): ... # no error!
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
|
||||
if not isinstance(DoesNotExist, type):
|
||||
reveal_type(DoesNotExist) # revealed: Unknown & ~type
|
||||
|
||||
class Foo(DoesNotExist): ... # error: [invalid-base]
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
```
|
||||
|
||||
## `__bases__` lists that cause errors at runtime
|
||||
|
||||
If the class's `__bases__` cause an exception to be raised at runtime and therefore the class
|
||||
|
|
|
@ -543,6 +543,13 @@ impl<'db> Type<'db> {
|
|||
Self::Dynamic(DynamicType::Unknown)
|
||||
}
|
||||
|
||||
pub(crate) fn into_dynamic(self) -> Option<DynamicType> {
|
||||
match self {
|
||||
Type::Dynamic(dynamic_type) => Some(dynamic_type),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn object(db: &'db dyn Db) -> Self {
|
||||
KnownClass::Object.to_instance(db)
|
||||
}
|
||||
|
|
|
@ -107,8 +107,19 @@ impl<'db> ClassBase<'db> {
|
|||
{
|
||||
Self::try_from_type(db, todo_type!("GenericAlias instance"))
|
||||
}
|
||||
Type::Intersection(inter) => {
|
||||
let dynamic_element = inter
|
||||
.positive(db)
|
||||
.iter()
|
||||
.find_map(|elem| elem.into_dynamic())?;
|
||||
|
||||
if ty.is_disjoint_from(db, KnownClass::Type.to_instance(db)) {
|
||||
None
|
||||
} else {
|
||||
Some(ClassBase::Dynamic(dynamic_element))
|
||||
}
|
||||
}
|
||||
Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs?
|
||||
Type::Intersection(_) => None, // TODO -- probably incorrect?
|
||||
Type::NominalInstance(_) => None, // TODO -- handle `__mro_entries__`?
|
||||
Type::PropertyInstance(_) => None,
|
||||
Type::Never
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue