diff --git a/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md b/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md index 96f3b84fc474b9..29284c1252025b 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md +++ b/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md @@ -142,7 +142,11 @@ reveal_type(A() + B()) # revealed: MyString # N.B. Still a subtype of `A`, even though `A` does not appear directly in the class's `__bases__` class C(B): ... -reveal_type(A() + C()) # revealed: MyString +# TODO: we currently only understand direct subclasses as subtypes of the superclass. +# We need to iterate through the full MRO rather than just the class's bases; +# if we do, we'll understand `C` as a subtype of `A`, and correctly understand this as being +# `MyString` rather than `str` +reveal_type(A() + C()) # revealed: str ``` ## Reflected precedence 2 diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 070bcbe1270293..89b735c4f5aea1 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -440,6 +440,9 @@ impl<'db> Type<'db> { .any(|&elem_ty| ty.is_subtype_of(db, elem_ty)), (_, Type::Instance(class)) if class.is_known(db, KnownClass::Object) => true, (Type::Instance(class), _) if class.is_known(db, KnownClass::Object) => false, + (Type::Instance(self_class), Type::Instance(target_class)) => self_class + .bases(db) // TODO: this should iterate through the MRO, not through the bases + .any(|base| base == Type::Class(target_class)), // TODO _ => false, } diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 3a8994e6cd1d12..a41b58da4aa315 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -2642,17 +2642,36 @@ impl<'db> TypeInferenceBuilder<'db> { op, ), - (Type::Instance(left_class), Type::Instance(right_class), op) => left_class - .class_member(self.db, op.dunder()) - .call(self.db, &[left_ty, right_ty]) - .return_ty(self.db) - .unwrap_or_else(|| { - right_class - .class_member(self.db, op.reflected_dunder()) - .call(self.db, &[right_ty, left_ty]) - .return_ty(self.db) - .unwrap_or(Type::Unknown) - }), + (Type::Instance(left_class), Type::Instance(right_class), op) => { + if left_class != right_class && right_ty.is_assignable_to(self.db, left_ty) { + let rhs_reflected = right_class.class_member(self.db, op.reflected_dunder()); + if !rhs_reflected.is_unbound() + && rhs_reflected != left_class.class_member(self.db, op.reflected_dunder()) + { + return rhs_reflected + .call(self.db, &[right_ty, left_ty]) + .return_ty(self.db) + .or_else(|| { + left_class + .class_member(self.db, op.dunder()) + .call(self.db, &[left_ty, right_ty]) + .return_ty(self.db) + }) + .unwrap_or(Type::Unknown); + } + } + left_class + .class_member(self.db, op.dunder()) + .call(self.db, &[left_ty, right_ty]) + .return_ty(self.db) + .or_else(|| { + right_class + .class_member(self.db, op.reflected_dunder()) + .call(self.db, &[right_ty, left_ty]) + .return_ty(self.db) + }) + .unwrap_or(Type::Unknown) + } _ => Type::Todo, // TODO }