Skip to content

Commit 41fa082

Browse files
authored
[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)
1 parent c7b6108 commit 41fa082

File tree

3 files changed

+42
-1
lines changed

3 files changed

+42
-1
lines changed

crates/ty_python_semantic/resources/mdtest/mro.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,29 @@ reveal_type(E.__mro__) # revealed: tuple[<class 'E'>, <class 'B'>, <class 'C'>,
154154
reveal_type(F.__mro__)
155155
```
156156

157+
## Inheritance with intersections that include `Unknown`
158+
159+
An intersection that includes `Unknown` or `Any` is permitted as long as the intersection is not
160+
disjoint from `type`.
161+
162+
```py
163+
from does_not_exist import DoesNotExist # error: [unresolved-import]
164+
165+
reveal_type(DoesNotExist) # revealed: Unknown
166+
167+
if hasattr(DoesNotExist, "__mro__"):
168+
reveal_type(DoesNotExist) # revealed: Unknown & <Protocol with members '__mro__'>
169+
170+
class Foo(DoesNotExist): ... # no error!
171+
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
172+
173+
if not isinstance(DoesNotExist, type):
174+
reveal_type(DoesNotExist) # revealed: Unknown & ~type
175+
176+
class Foo(DoesNotExist): ... # error: [invalid-base]
177+
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
178+
```
179+
157180
## `__bases__` lists that cause errors at runtime
158181

159182
If the class's `__bases__` cause an exception to be raised at runtime and therefore the class

crates/ty_python_semantic/src/types.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,13 @@ impl<'db> Type<'db> {
543543
Self::Dynamic(DynamicType::Unknown)
544544
}
545545

546+
pub(crate) fn into_dynamic(self) -> Option<DynamicType> {
547+
match self {
548+
Type::Dynamic(dynamic_type) => Some(dynamic_type),
549+
_ => None,
550+
}
551+
}
552+
546553
pub fn object(db: &'db dyn Db) -> Self {
547554
KnownClass::Object.to_instance(db)
548555
}

crates/ty_python_semantic/src/types/class_base.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,19 @@ impl<'db> ClassBase<'db> {
107107
{
108108
Self::try_from_type(db, todo_type!("GenericAlias instance"))
109109
}
110+
Type::Intersection(inter) => {
111+
let dynamic_element = inter
112+
.positive(db)
113+
.iter()
114+
.find_map(|elem| elem.into_dynamic())?;
115+
116+
if ty.is_disjoint_from(db, KnownClass::Type.to_instance(db)) {
117+
None
118+
} else {
119+
Some(ClassBase::Dynamic(dynamic_element))
120+
}
121+
}
110122
Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs?
111-
Type::Intersection(_) => None, // TODO -- probably incorrect?
112123
Type::NominalInstance(_) => None, // TODO -- handle `__mro_entries__`?
113124
Type::PropertyInstance(_) => None,
114125
Type::Never

0 commit comments

Comments
 (0)