diff --git a/numpy/_core/_exceptions.py b/numpy/_core/_exceptions.py index 87d4213a6d42..01dbadf9f64a 100644 --- a/numpy/_core/_exceptions.py +++ b/numpy/_core/_exceptions.py @@ -38,26 +38,30 @@ def __init__(self, ufunc): @_display_as_base class _UFuncNoLoopError(UFuncTypeError): """ Thrown when a ufunc loop cannot be found """ - def __init__(self, ufunc, dtypes): + def __init__(self, ufunc, dtypes, ambiguous_promoter): super().__init__(ufunc) self.dtypes = tuple(dtypes) + self.ambiguous_promoter = ambiguous_promoter def __str__(self): - return ( + err = ( "ufunc {!r} did not contain a loop with signature matching types " - "{!r} -> {!r}" + "{!r} -> {!r}." ).format( self.ufunc.__name__, _unpack_tuple(self.dtypes[:self.ufunc.nin]), _unpack_tuple(self.dtypes[self.ufunc.nin:]) ) + if self.ambiguous_promoter: + err += " (ambiguous promoters found)" + return err @_display_as_base class _UFuncBinaryResolutionError(_UFuncNoLoopError): """ Thrown when a binary resolution fails """ def __init__(self, ufunc, dtypes): - super().__init__(ufunc, dtypes) + super().__init__(ufunc, dtypes, False) assert len(self.dtypes) == 2 def __str__(self): diff --git a/numpy/_core/src/umath/dispatching.c b/numpy/_core/src/umath/dispatching.c index fbba26ea247b..289574fbdc13 100644 --- a/numpy/_core/src/umath/dispatching.c +++ b/numpy/_core/src/umath/dispatching.c @@ -447,14 +447,7 @@ resolve_implementation_info(PyUFuncObject *ufunc, PyObject *given = PyArray_TupleFromItems( ufunc->nargs, (PyObject **)op_dtypes, 1); if (given != NULL) { - PyErr_Format(PyExc_RuntimeError, - "Could not find a loop for the inputs:\n %S\n" - "The two promoters %S and %S matched the input " - "equally well. Promoters must be designed " - "to be unambiguous. NOTE: This indicates an error " - "in NumPy or an extending library and should be " - "reported.", - given, best_dtypes, curr_dtypes); + raise_no_loop_found_error(ufunc, (PyObject **)op_dtypes, 1); Py_DECREF(given); } *out_info = NULL; @@ -1053,7 +1046,7 @@ promote_and_get_ufuncimpl(PyUFuncObject *ufunc, handle_error: /* We only set the "no loop found error here" */ if (!PyErr_Occurred()) { - raise_no_loop_found_error(ufunc, (PyObject **)op_dtypes); + raise_no_loop_found_error(ufunc, (PyObject **)op_dtypes, 0); } /* * Otherwise an error occurred, but if the error was DTypePromotionError @@ -1063,7 +1056,7 @@ promote_and_get_ufuncimpl(PyUFuncObject *ufunc, else if (PyErr_ExceptionMatches(npy_DTypePromotionError)) { PyObject *err_type = NULL, *err_value = NULL, *err_traceback = NULL; PyErr_Fetch(&err_type, &err_value, &err_traceback); - raise_no_loop_found_error(ufunc, (PyObject **)op_dtypes); + raise_no_loop_found_error(ufunc, (PyObject **)op_dtypes, 0); npy_PyErr_ChainExceptionsCause(err_type, err_value, err_traceback); } return NULL; diff --git a/numpy/_core/src/umath/ufunc_type_resolution.c b/numpy/_core/src/umath/ufunc_type_resolution.c index 4975d41147ea..2561ec39c7fc 100644 --- a/numpy/_core/src/umath/ufunc_type_resolution.c +++ b/numpy/_core/src/umath/ufunc_type_resolution.c @@ -106,7 +106,7 @@ raise_binary_type_reso_error(PyUFuncObject *ufunc, PyArrayObject **operands) { */ NPY_NO_EXPORT int raise_no_loop_found_error( - PyUFuncObject *ufunc, PyObject **dtypes) + PyUFuncObject *ufunc, PyObject **dtypes, int ambiguous_promoter) { static PyObject *exc_type = NULL; @@ -122,7 +122,8 @@ raise_no_loop_found_error( return -1; } /* produce an error object */ - PyObject *exc_value = PyTuple_Pack(2, ufunc, dtypes_tup); + PyObject *exc_value = PyTuple_Pack(3, ufunc, dtypes_tup, + ambiguous_promoter ? Py_True : Py_False); Py_DECREF(dtypes_tup); if (exc_value == NULL) { return -1; @@ -553,7 +554,7 @@ PyUFunc_SimpleUniformOperationTypeResolver( out_dtypes[iop] = PyArray_DESCR(operands[iop]); Py_INCREF(out_dtypes[iop]); } - raise_no_loop_found_error(ufunc, (PyObject **)out_dtypes); + raise_no_loop_found_error(ufunc, (PyObject **)out_dtypes, 0); for (iop = 0; iop < ufunc->nin; iop++) { Py_DECREF(out_dtypes[iop]); out_dtypes[iop] = NULL; @@ -1578,7 +1579,7 @@ PyUFunc_DefaultLegacyInnerLoopSelector(PyUFuncObject *ufunc, types += nargs; } - return raise_no_loop_found_error(ufunc, (PyObject **)dtypes); + return raise_no_loop_found_error(ufunc, (PyObject **)dtypes, 0); } diff --git a/numpy/_core/src/umath/ufunc_type_resolution.h b/numpy/_core/src/umath/ufunc_type_resolution.h index 84a2593f44c4..a7937f1a4c06 100644 --- a/numpy/_core/src/umath/ufunc_type_resolution.h +++ b/numpy/_core/src/umath/ufunc_type_resolution.h @@ -140,6 +140,6 @@ PyUFunc_DefaultLegacyInnerLoopSelector(PyUFuncObject *ufunc, int *out_needs_api); NPY_NO_EXPORT int -raise_no_loop_found_error(PyUFuncObject *ufunc, PyObject **dtypes); +raise_no_loop_found_error(PyUFuncObject *ufunc, PyObject **dtypes, int ambiguous_promoter); #endif diff --git a/numpy/_core/tests/test_stringdtype.py b/numpy/_core/tests/test_stringdtype.py index de41506f653a..fef6d73982fc 100644 --- a/numpy/_core/tests/test_stringdtype.py +++ b/numpy/_core/tests/test_stringdtype.py @@ -765,6 +765,12 @@ def test_multiply_reduce(): assert res == val * np.prod(repeats) +def test_multiply_two_string_raises(): + arr = np.array(["hello", "world"]) + with pytest.raises(TypeError): + np.multiply(arr, arr) + + @pytest.mark.parametrize("use_out", [True, False]) @pytest.mark.parametrize("other", [2, [2, 1, 3, 4, 1, 3]]) @pytest.mark.parametrize(