Skip to content

Commit

Permalink
Fix precedence for reflected dunders if the r.h.s. is a subtype that …
Browse files Browse the repository at this point in the history
…overrides the reflected dunder
  • Loading branch information
AlexWaygood committed Oct 18, 2024
1 parent 8df9440 commit 95a6163
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
41 changes: 30 additions & 11 deletions crates/red_knot_python_semantic/src/types/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 95a6163

Please sign in to comment.