diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 011484f26bfee..382448729f64b 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1194,19 +1194,22 @@ impl<'db> Type<'db> { ty.bool(db).is_always_true() } - (Type::SubclassOf(_), _) | (_, Type::SubclassOf(_)) => { - // TODO: Once we have support for final classes, we can determine disjointness in some cases + (Type::SubclassOf(_), other) | (other, Type::SubclassOf(_)) => { + // TODO: Once we have support for final classes, we can determine disjointness in more cases // here. However, note that it might be better to turn `Type::SubclassOf('FinalClass')` into // `Type::ClassLiteral('FinalClass')` during construction, instead of adding special cases for // final classes inside `Type::SubclassOf` everywhere. - false + other.is_disjoint_from(db, KnownClass::Type.to_instance(db)) } - (Type::KnownInstance(left), right) => { - left.instance_fallback(db).is_disjoint_from(db, right) + (Type::KnownInstance(known_instance), Type::Instance(InstanceType { class })) + | (Type::Instance(InstanceType { class }), Type::KnownInstance(known_instance)) => { + !known_instance.is_instance_of(db, class) } - (left, Type::KnownInstance(right)) => { - left.is_disjoint_from(db, right.instance_fallback(db)) + + (known_instance_ty @ Type::KnownInstance(_), Type::Tuple(_)) + | (Type::Tuple(_), known_instance_ty @ Type::KnownInstance(_)) => { + known_instance_ty.is_disjoint_from(db, KnownClass::Tuple.to_instance(db)) } ( @@ -2352,6 +2355,15 @@ impl<'db> KnownClass { .unwrap_or(Type::subclass_of_base(ClassBase::Unknown)) } + /// Return `true` if this symbol can be resolved to a class definition `class` in typeshed, + /// *and* `class` is a subclass of `other`. + pub fn is_subclass_of(self, db: &'db dyn Db, other: Class<'db>) -> bool { + known_module_symbol(db, self.canonical_module(db), self.as_str()) + .ignore_possibly_unbound() + .and_then(Type::into_class_literal) + .is_some_and(|ClassLiteralType { class }| class.is_subclass_of(db, other)) + } + /// Return the module in which we should look up the definition for this class pub(crate) fn canonical_module(self, db: &'db dyn Db) -> KnownModule { match self { @@ -2738,6 +2750,10 @@ impl<'db> KnownInstanceType<'db> { self.class().to_instance(db) } + pub fn is_instance_of(self, db: &'db dyn Db, class: Class<'db>) -> bool { + self.class().is_subclass_of(db, class) + } + pub fn try_from_file_and_name(db: &'db dyn Db, file: File, symbol_name: &str) -> Option { let candidate = match symbol_name { "Any" => Self::Any, @@ -4160,6 +4176,8 @@ pub(crate) mod tests { #[test_case(Ty::SubclassOfBuiltinClass("object"), Ty::None)] #[test_case(Ty::SubclassOfBuiltinClass("str"), Ty::LiteralString)] #[test_case(Ty::AlwaysFalsy, Ty::AlwaysTruthy)] + #[test_case(Ty::Tuple(vec![]), Ty::TypingLiteral)] + #[test_case(Ty::TypingLiteral, Ty::SubclassOfBuiltinClass("object"))] fn is_disjoint_from(a: Ty, b: Ty) { let db = setup_db(); let a = a.into_type(&db);