mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-30 08:23:53 +00:00
[red-knot] Fix edge case for binary-expression inference where the lhs and rhs are the exact same type (#13823)
## Summary This fixes an edge case that @carljm and I missed when implementing https://github.com/astral-sh/ruff/pull/13800. Namely, if the left-hand operand is the _exact same type_ as the right-hand operand, the reflected dunder on the right-hand operand is never tried: ```pycon >>> class Foo: ... def __radd__(self, other): ... return 42 ... >>> Foo() + Foo() Traceback (most recent call last): File "<python-input-1>", line 1, in <module> Foo() + Foo() ~~~~~~^~~~~~~ TypeError: unsupported operand type(s) for +: 'Foo' and 'Foo' ``` This edge case _is_ covered in Brett's blog at https://snarky.ca/unravelling-binary-arithmetic-operations-in-python/, but I missed it amongst all the other subtleties of this algorithm. The motivations and history behind it were discussed in https://mail.python.org/archives/list/python-dev@python.org/thread/7NZUCODEAPQFMRFXYRMGJXDSIS3WJYIV/ ## Test Plan I added an mdtest for this cornercase.
This commit is contained in:
parent
f4b5e70fae
commit
55bccf6680
2 changed files with 27 additions and 4 deletions
|
@ -458,6 +458,25 @@ reveal_type(C() + A())
|
||||||
reveal_type(B() + C())
|
reveal_type(B() + C())
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Reflected dunder is not tried between two objects of the same type
|
||||||
|
|
||||||
|
For the specific case where the left-hand operand is the exact same type as the
|
||||||
|
right-hand operand, the reflected dunder of the right-hand operand is not
|
||||||
|
tried; the runtime short-circuits after trying the unreflected dunder of the
|
||||||
|
left-hand operand. For context, see
|
||||||
|
[this mailing list discussion](https://mail.python.org/archives/list/python-dev@python.org/thread/7NZUCODEAPQFMRFXYRMGJXDSIS3WJYIV/).
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Foo:
|
||||||
|
def __radd__(self, other: Foo) -> Foo:
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
# error: [unsupported-operator]
|
||||||
|
# revealed: Unknown
|
||||||
|
reveal_type(Foo() + Foo())
|
||||||
|
```
|
||||||
|
|
||||||
### Wrong type
|
### Wrong type
|
||||||
|
|
||||||
TODO: check signature and error if `other` is the wrong type
|
TODO: check signature and error if `other` is the wrong type
|
||||||
|
|
|
@ -2682,10 +2682,14 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.call(self.db, &[left_ty, right_ty])
|
.call(self.db, &[left_ty, right_ty])
|
||||||
.return_ty(self.db)
|
.return_ty(self.db)
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
right_class
|
if left_class == right_class {
|
||||||
.class_member(self.db, op.reflected_dunder())
|
None
|
||||||
.call(self.db, &[right_ty, left_ty])
|
} else {
|
||||||
.return_ty(self.db)
|
right_class
|
||||||
|
.class_member(self.db, op.reflected_dunder())
|
||||||
|
.call(self.db, &[right_ty, left_ty])
|
||||||
|
.return_ty(self.db)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue