Description
If we make a class like this:
#[pyclass]
#[derive(Debug)]
struct RhsArithmetic {}
#[pymethods]
impl RhsArithmetic {
#[new]
fn new() -> RhsArithmetic {
RhsArithmetic {}
}
}
#[pyproto]
impl PyNumberProtocol for RhsArithmetic {
fn __mul__(lhs: PyRef<'p, Self>, rhs: &PyAny) -> PyResult<String> {
Ok(format!("MUL"))
}
fn __rmul__(&self, other: &PyAny) -> PyResult<String> {
Ok(format!("RMUL"))
}
}
Then both 1 * rhs
and rhs * 1
call the function in the tp_as_number.nb_mul
slot. As of #839 this will be our __mul__
implementation above (and never __rmul__
).
And specifically it will call it with the arguments in the same order as invoked. So from above, we will have __mul__(1, rhs)
and __mul__(rhs, 1)
.
This is problematic, because __mul__
as defined above will raise a TypeError
if the first argument is not Self
.
We saw this behaviour reported on Gitter. All three of the below will currently be calling __mul__
, which is why the second will TypeError
:
>>> rhs * 1.0
'MUL'
>>> 1.0 * rhs # should call __rmul__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError
>>> rhs * rhs
'MUL'
I am thinking that instead of doing this, if both __mul__
and __rmul__
are defined we should be creating a wrapper function which calls the correct function depending on the type of the first argument.
The implementation of PyNumber_Add
suggests that we should return Py_NotImplemented
from this wrapper function if there is a type error, so that sq_concat
is given a chance to be called also.