diff --git a/numpy/core/src/umath/dispatching.c b/numpy/core/src/umath/dispatching.c index b8f102b3dff2..2be2d9e17149 100644 --- a/numpy/core/src/umath/dispatching.c +++ b/numpy/core/src/umath/dispatching.c @@ -914,8 +914,13 @@ promote_and_get_ufuncimpl(PyUFuncObject *ufunc, signature[i] = (PyArray_DTypeMeta *)PyTuple_GET_ITEM(all_dtypes, i); Py_INCREF(signature[i]); } - else { - assert((PyObject *)signature[i] == PyTuple_GET_ITEM(all_dtypes, i)); + else if ((PyObject *)signature[i] != PyTuple_GET_ITEM(all_dtypes, i)) { + /* + * If signature is forced the cache may contain an incompatible + * loop found via promotion (signature not enforced). Reject it. + */ + raise_no_loop_found_error(ufunc, (PyObject **)op_dtypes); + return NULL; } } diff --git a/numpy/core/tests/test_ufunc.py b/numpy/core/tests/test_ufunc.py index 852044d32fcc..d1510c6fdbe8 100644 --- a/numpy/core/tests/test_ufunc.py +++ b/numpy/core/tests/test_ufunc.py @@ -492,6 +492,15 @@ def test_partial_signature_mismatch(self, casting): with pytest.raises(TypeError): np.ldexp(1., np.uint64(3), signature=(None, None, "d")) + def test_partial_signature_mismatch_with_cache(self): + with pytest.raises(TypeError): + np.add(np.float16(1), np.uint64(2), sig=("e", "d", None)) + # Ensure e,d->None is in the dispatching cache (double loop) + np.add(np.float16(1), np.float64(2)) + # The error must still be raised: + with pytest.raises(TypeError): + np.add(np.float16(1), np.uint64(2), sig=("e", "d", None)) + def test_use_output_signature_for_all_arguments(self): # Test that providing only `dtype=` or `signature=(None, None, dtype)` # is sufficient if falling back to a homogeneous signature works. diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py index f12307e30ba4..4160dc9b1ffb 100644 --- a/numpy/core/tests/test_umath.py +++ b/numpy/core/tests/test_umath.py @@ -226,6 +226,14 @@ def __ne__(self, other): a = np.array([np.nan], dtype=object) assert_equal(np.not_equal(a, a), [True]) + def test_error_in_equal_reduce(self): + # gh-20929 + # make sure np.equal.reduce raises a TypeError if an array is passed + # without specifying the dtype + a = np.array([0, 0]) + assert_equal(np.equal.reduce(a, dtype=bool), True) + assert_raises(TypeError, np.equal.reduce, a) + class TestAdd: def test_reduce_alignment(self):