2121#include "ctors.h"
2222#include "dtypemeta.h"
2323#include "usertypes.h"
24+ #include "number.h"
2425#include "numpyos.h"
2526#include "can_cast_table.h"
2627#include "common.h"
@@ -120,19 +121,6 @@ gentype_free(PyObject *v)
120121}
121122
122123
123- static PyObject *
124- gentype_power (PyObject * m1 , PyObject * m2 , PyObject * modulo )
125- {
126- if (modulo != Py_None ) {
127- /* modular exponentiation is not implemented (gh-8804) */
128- Py_INCREF (Py_NotImplemented );
129- return Py_NotImplemented ;
130- }
131-
132- BINOP_GIVE_UP_IF_NEEDED (m1 , m2 , nb_power , gentype_power );
133- return PyArray_Type .tp_as_number -> nb_power (m1 , m2 , Py_None );
134- }
135-
136124static PyObject *
137125gentype_generic_method (PyObject * self , PyObject * args , PyObject * kwds ,
138126 char * str )
@@ -164,33 +152,194 @@ gentype_generic_method(PyObject *self, PyObject *args, PyObject *kwds,
164152 }
165153}
166154
167- static PyObject *
168- gentype_add (PyObject * m1 , PyObject * m2 )
169- {
170- /* special case str.__radd__, which should not call array_add */
171- if (PyBytes_Check (m1 ) || PyUnicode_Check (m1 )) {
172- Py_INCREF (Py_NotImplemented );
173- return Py_NotImplemented ;
155+
156+ /*
157+ * Helper function to deal with binary operator deferral. Must be passed a
158+ * valid self (a generic scalar) and an other item.
159+ * May fill self_item and/or other_arr (but not both) with non-NULL values.
160+ *
161+ * Why this dance? When the other object is a exactly Python scalar something
162+ * awkward happens historically in NumPy.
163+ * NumPy doesn't define a result, but the ufunc would cast to `astype(object)`
164+ * which is the same as `scalar.item()`. And that operation converts e.g.
165+ * float32 or float64 to Python floats.
166+ * It then retries. And because it is a builtin type now the operation may
167+ * succeed.
168+ *
169+ * This retrying pass only makes sense if the other object is a Python
170+ * scalar (otherwise we fill in `other_arr` which can be used to call the
171+ * ufunc).
172+ * Additionally, if `self.item()` has the same type as `self` we would end up
173+ * in an infinite recursion.
174+ *
175+ * So the result of this function means the following:
176+ * - < 0 error return.
177+ * - self_op is filled in: Retry the Python operator.
178+ * - other_op is filled in: Use the array operator (goes into ufuncs)
179+ * (This may be the original generic if it is one.)
180+ * - neither is filled in: Return NotImplemented.
181+ *
182+ * It is not possible for both to be filled. If `other` is also a generics,
183+ * it is returned.
184+ */
185+ static inline int
186+ find_binary_operation_path (
187+ PyObject * self , PyObject * other , PyObject * * self_op , PyObject * * other_op )
188+ {
189+ * other_op = NULL ;
190+ * self_op = NULL ;
191+
192+ if (PyArray_IsScalar (other , Generic ) ||
193+ PyLong_CheckExact (other ) ||
194+ PyFloat_CheckExact (other ) ||
195+ PyComplex_CheckExact (other ) ||
196+ PyBool_Check (other )) {
197+ /*
198+ * The other operand is ready for the operation already. Must pass on
199+ * on float/long/complex mainly for weak promotion (NEP 50).
200+ */
201+ Py_INCREF (other );
202+ * other_op = other ;
203+ return 0 ;
174204 }
175- BINOP_GIVE_UP_IF_NEEDED (m1 , m2 , nb_add , gentype_add );
176- return PyArray_Type .tp_as_number -> nb_add (m1 , m2 );
205+
206+ /*
207+ * Now check `other`. We want to know whether it is an object scalar
208+ * and the easiest way is by converting to an array here.
209+ */
210+ int was_scalar ;
211+ PyArrayObject * arr = (PyArrayObject * )PyArray_FromAny_int (
212+ other , NULL , NULL , 0 , 0 , 0 , NULL , & was_scalar );
213+ if (arr == NULL ) {
214+ return -1 ;
215+ }
216+
217+ if (!was_scalar || PyArray_DESCR (arr )-> type_num != NPY_OBJECT ) {
218+ /* The array is OK for usage and we can simply forward it
219+ *
220+ * NOTE: Future NumPy may need to distinguish scalars here, one option
221+ * could be marking the array.
222+ */
223+ * other_op = (PyObject * )arr ;
224+ return 0 ;
225+ }
226+ Py_DECREF (arr );
227+
228+ /*
229+ * If we are here, we need to operate on Python scalars. In general
230+ * that would just fails since NumPy doesn't know the other object!
231+ *
232+ * However, NumPy (historically) often makes this work magically because
233+ * it object ufuncs end up casting to object with `.item()` and that may
234+ * returns Python type often (e.g. float for float32, float64)!
235+ * Retrying then succeeds. So if (and only if) `self.item()` returns a new
236+ * type, we can safely attempt the operation (again) with that.
237+ */
238+ PyObject * self_item = PyObject_CallMethodNoArgs (self , npy_interned_str .item );
239+ if (self_item == NULL ) {
240+ return -1 ;
241+ }
242+ if (Py_TYPE (self_item ) != Py_TYPE (self )) {
243+ /* self_item can be used to retry the operation */
244+ * self_op = self_item ;
245+ return 0 ;
246+ }
247+ /* The operation can't work and we will return NotImplemented */
248+ return 0 ;
177249}
178250
251+
252+ /*
253+ * These are defined below as they require special handling, we still define
254+ * a _gen version here. `power` is special as it has three arguments.
255+ */
256+ static PyObject *
257+ gentype_add (PyObject * m1 , PyObject * m2 );
258+
259+ static PyObject *
260+ gentype_multiply (PyObject * m1 , PyObject * m2 );
261+
262+
179263/**begin repeat
180264 *
181- * #name = subtract, remainder, divmod, lshift, rshift,
182- * and, xor, or, floor_divide, true_divide#
265+ * #name = add, multiply, subtract, remainder, divmod,
266+ * lshift, rshift, and, xor, or, floor_divide, true_divide#
267+ * #ufunc = add, multiply, subtract, remainder, divmod,
268+ * left_shift, right_shift, bitwise_and, bitwise_xor, bitwise_or,
269+ * floor_divide, true_divide#
270+ * #func = Add, Multiply, Subtract, Remainder, Divmod,
271+ * Lshift, Rshift, And, Xor, Or, FloorDivide, TrueDivide#
272+ * #suff = _gen, _gen,,,,,,,,,,#
183273 */
274+ /* NOTE: We suffix the name for functions requiring special handling first. */
184275static PyObject *
185- gentype_ @name @(PyObject * m1 , PyObject * m2 )
276+ gentype_ @name @@ suff @ (PyObject * m1 , PyObject * m2 )
186277{
187278 BINOP_GIVE_UP_IF_NEEDED (m1 , m2 , nb_ @name @, gentype_ @name @);
188- return PyArray_Type .tp_as_number -> nb_ @name @(m1 , m2 );
279+
280+ PyObject * self = NULL ;
281+ PyObject * other = NULL ;
282+ PyObject * self_op , * other_op ;
283+
284+ if (!PyArray_IsScalar (m2 , Generic )) {
285+ self = m1 ;
286+ other = m2 ;
287+ }
288+ else {
289+ self = m2 ;
290+ other = m1 ;
291+ }
292+ if (find_binary_operation_path (self , other , & self_op , & other_op ) < 0 ) {
293+ return NULL ;
294+ }
295+ if (self_op != NULL ) {
296+ PyObject * res ;
297+ if (self == m1 ) {
298+ res = PyNumber_ @func @(self_op , m2 );
299+ }
300+ else {
301+ res = PyNumber_ @func @(m1 , self_op );
302+ }
303+ Py_DECREF (self_op );
304+ return res ;
305+ }
306+ else if (other_op != NULL ) {
307+ /* Call the corresponding ufunc (with the array) */
308+ PyObject * res ;
309+ if (self == m1 ) {
310+ res = PyArray_GenericBinaryFunction (m1 , other_op , n_ops .@ufunc @);
311+ }
312+ else {
313+ res = PyArray_GenericBinaryFunction (other_op , m2 , n_ops .@ufunc @);
314+ }
315+ Py_DECREF (other_op );
316+ return res ;
317+ }
318+ else {
319+ assert (other_op == NULL );
320+ Py_RETURN_NOTIMPLEMENTED ;
321+ }
189322}
190323
191324/**end repeat**/
192325
193- /* Get a nested slot, or NULL if absent */
326+ /*
327+ * The following operators use the above, but require specialization.
328+ */
329+
330+ static PyObject *
331+ gentype_add (PyObject * m1 , PyObject * m2 )
332+ {
333+ /* special case str.__radd__, which should not call array_add */
334+ if (PyBytes_Check (m1 ) || PyUnicode_Check (m1 )) {
335+ Py_INCREF (Py_NotImplemented );
336+ return Py_NotImplemented ;
337+ }
338+
339+ return gentype_add_gen (m1 , m2 );
340+ }
341+
342+ /* Get a nested slot, or NULL if absent (for multiply implementation) */
194343#define GET_NESTED_SLOT (type , group , slot ) \
195344 ((type)->group == NULL ? NULL : (type)->group->slot)
196345
@@ -219,11 +368,75 @@ gentype_multiply(PyObject *m1, PyObject *m2)
219368 Py_INCREF (Py_NotImplemented );
220369 return Py_NotImplemented ;
221370 }
222- /* All normal cases are handled by PyArray's multiply */
223- BINOP_GIVE_UP_IF_NEEDED (m1 , m2 , nb_multiply , gentype_multiply );
224- return PyArray_Type .tp_as_number -> nb_multiply (m1 , m2 );
371+
372+ return gentype_multiply_gen (m1 , m2 );
225373}
226374
375+
376+ /*
377+ * NOTE: The three argument nature of power requires code duplication here.
378+ */
379+ static PyObject *
380+ gentype_power (PyObject * m1 , PyObject * m2 , PyObject * modulo )
381+ {
382+ if (modulo != Py_None ) {
383+ /* modular exponentiation is not implemented (gh-8804) */
384+ Py_INCREF (Py_NotImplemented );
385+ return Py_NotImplemented ;
386+ }
387+
388+ BINOP_GIVE_UP_IF_NEEDED (m1 , m2 , nb_power , gentype_power );
389+
390+ PyObject * self = NULL ;
391+ PyObject * other = NULL ;
392+ PyObject * self_op , * other_op ;
393+
394+ if (!PyArray_IsScalar (m2 , Generic )) {
395+ self = m1 ;
396+ other = m2 ;
397+ }
398+ else {
399+ self = m2 ;
400+ other = m1 ;
401+ }
402+ if (find_binary_operation_path (self , other , & self_op , & other_op ) < 0 ) {
403+ return NULL ;
404+ }
405+ if (self_op != NULL ) {
406+ PyObject * res ;
407+ if (self == m1 ) {
408+ res = PyNumber_Power (self_op , m2 , Py_None );
409+ }
410+ else {
411+ res = PyNumber_Power (m1 , self_op , Py_None );
412+ }
413+ Py_DECREF (self_op );
414+ return res ;
415+ }
416+ else if (other_op != NULL ) {
417+ /* Call the corresponding ufunc (with the array)
418+ * NOTE: As of NumPy 2.0 there are inconsistencies in array_power
419+ * calling it would fail a (niche) test because an array is
420+ * returned in one of the fast-paths.
421+ * (once NumPy propagates 0-D arrays, this is irrelevant)
422+ */
423+ PyObject * res ;
424+ if (self == m1 ) {
425+ res = PyArray_GenericBinaryFunction (m1 , other_op , n_ops .power );
426+ }
427+ else {
428+ res = PyArray_GenericBinaryFunction (other_op , m2 , n_ops .power );
429+ }
430+ Py_DECREF (other_op );
431+ return res ;
432+ }
433+ else {
434+ assert (other_op == NULL );
435+ Py_RETURN_NOTIMPLEMENTED ;
436+ }
437+ }
438+
439+
227440/**begin repeat
228441 * #TYPE = BYTE, UBYTE, SHORT, USHORT, INT, UINT,
229442 * LONG, ULONG, LONGLONG, ULONGLONG#
@@ -1265,8 +1478,6 @@ static PyNumberMethods gentype_as_number = {
12651478static PyObject *
12661479gentype_richcompare (PyObject * self , PyObject * other , int cmp_op )
12671480{
1268- PyObject * arr , * ret ;
1269-
12701481 /*
12711482 * If the other object is None, False is always right. This avoids
12721483 * the array None comparison, at least until deprecation it is fixed.
@@ -1287,17 +1498,35 @@ gentype_richcompare(PyObject *self, PyObject *other, int cmp_op)
12871498
12881499 RICHCMP_GIVE_UP_IF_NEEDED (self , other );
12891500
1290- arr = PyArray_FromScalar (self , NULL );
1291- if (arr == NULL ) {
1501+ PyObject * self_op ;
1502+ PyObject * other_op ;
1503+ if (find_binary_operation_path (self , other , & self_op , & other_op ) < 0 ) {
12921504 return NULL ;
12931505 }
1294- /*
1295- * Call via PyObject_RichCompare to ensure that other.__eq__
1296- * has a chance to run when necessary
1297- */
1298- ret = PyObject_RichCompare (arr , other , cmp_op );
1299- Py_DECREF (arr );
1300- return ret ;
1506+
1507+ /* We can always just call RichCompare again */
1508+ if (other_op != NULL ) {
1509+ /* If we use richcompare again, need to ensure that one op is array */
1510+ self_op = PyArray_FromScalar (self , NULL );
1511+ if (self_op == NULL ) {
1512+ Py_DECREF (other_op );
1513+ return NULL ;
1514+ }
1515+ PyObject * res = PyObject_RichCompare (self_op , other_op , cmp_op );
1516+ Py_DECREF (self_op );
1517+ Py_DECREF (other_op );
1518+ return res ;
1519+ }
1520+ else if (self_op != NULL ) {
1521+ /* Try again, since other is an object scalar and this one mutated */
1522+ PyObject * res = PyObject_RichCompare (self_op , other , cmp_op );
1523+ Py_DECREF (self_op );
1524+ return res ;
1525+ }
1526+ else {
1527+ /* Comparison with arbitrary objects cannot be defined. */
1528+ Py_RETURN_NOTIMPLEMENTED ;
1529+ }
13011530}
13021531
13031532static PyObject *
0 commit comments