|
| 1 | +- Start Date: 2023-10-30 |
| 2 | +- RFC PR: [amaranth-lang/rfcs#0028](https://github.com/amaranth-lang/rfcs/pull/0028) |
| 3 | +- Amaranth Issue: [amaranth-lang/amaranth#0929](https://github.com/amaranth-lang/amaranth/issues/0929) |
| 4 | + |
| 5 | +# Allow overriding Value operators |
| 6 | + |
| 7 | +## Summary |
| 8 | +[summary]: #summary |
| 9 | + |
| 10 | +Allow overriding binary `Value` operators with reflected operators in a value-castable type. |
| 11 | + |
| 12 | +## Motivation |
| 13 | +[motivation]: #motivation |
| 14 | + |
| 15 | +A value-castable type can define operators that return another value-castable. |
| 16 | +However, if the left side operand is a `Value`, its operator will be called first, casting the right side operand to a plain `Value`. |
| 17 | +This creates a mismatch in behavior depending on the type and order of operands. |
| 18 | + |
| 19 | +As an example, consider the multiplication of a fixed point value-castable with an integral type: |
| 20 | +``` |
| 21 | +>>> Q(7).const(0.5) * 255 |
| 22 | +(fixedpoint Q8.7 (* (const 8'sd64) (const 8'd255))) |
| 23 | +>>> 255 * Q(7).const(0.5) |
| 24 | +(fixedpoint Q8.7 (* (const 8'sd64) (const 8'd255))) |
| 25 | +>>> Q(7).const(0.5) * C(255) |
| 26 | +(fixedpoint Q8.7 (* (const 8'sd64) (const 8'd255))) |
| 27 | +>>> C(255) * Q(7).const(0.5) |
| 28 | +(* (const 8'd255) (const 8'sd64)) |
| 29 | +``` |
| 30 | + |
| 31 | +## Explanation |
| 32 | +[guide-level-explanation]: #guide-level-explanation |
| 33 | + |
| 34 | +When a binary `Value` operator is called with a value-castable `other`, check whether the value-castable implements the reflected variant of the operator first and defer to it when present. |
| 35 | + |
| 36 | +## Drawbacks |
| 37 | +[drawbacks]: #drawbacks |
| 38 | + |
| 39 | +Extra logic required around every `Value` operator. |
| 40 | + |
| 41 | +## Prior art |
| 42 | +[prior-art]: #prior-art |
| 43 | + |
| 44 | +This is standard behavior for inheritance in [Python](https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types): |
| 45 | + |
| 46 | +> Note: If the right operand’s type is a subclass of the left operand’s type and that subclass provides a different implementation of the reflected method for the operation, this method will be called before the left operand’s non-reflected method. This behavior allows subclasses to override their ancestors’ operations. |
| 47 | +
|
| 48 | +We don't get this behavior automatically because `Value` is not an ancestor of `ValueCastable`, but it would make sense for it to behave as it were. |
| 49 | + |
| 50 | +## Rationale and alternatives |
| 51 | +[rationale-and-alternatives]: #rationale-and-alternatives |
| 52 | + |
| 53 | +As an alternative, `Value` and `ValueCastable` could be rearchitected so that `ValueCastable` inherits from either `Value` or a common base that implements the `Value` operators. |
| 54 | +This would make Python do the right thing w.r.t. operator overriding, but is a larger change with more potential for undesirable consequences. |
| 55 | + |
| 56 | + |
| 57 | +## Unresolved questions |
| 58 | +[unresolved-questions]: #unresolved-questions |
| 59 | + |
| 60 | +None. |
| 61 | + |
| 62 | +## Future possibilities |
| 63 | +[future-possibilities]: #future-possibilities |
| 64 | + |
| 65 | +`Value.eq()` could in the same manner check for and defer to a `.req()` method, i.e. reflected `.eq()`, to allow a value-castable to override how assignment from it to a `Value` is handled. |
0 commit comments