From f4f7595e99fffffe15effdaefe12cb7ef686ec6f Mon Sep 17 00:00:00 2001 From: Saransh Date: Thu, 14 Jul 2022 14:10:46 +0530 Subject: [PATCH 1/2] TST: ensure `np.equal.reduce` raises a `TypeError` --- numpy/core/tests/test_umath.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py index a696fceb8a1b..2accb6386b54 100644 --- a/numpy/core/tests/test_umath.py +++ b/numpy/core/tests/test_umath.py @@ -267,6 +267,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): From e3bcb6a7e54239ee83e0f9ca80cf71dd8f35bec9 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Fri, 5 Aug 2022 10:11:08 +0530 Subject: [PATCH 2/2] BUG: Replace assert with correct error If we cache a promoted version of the loop, that promoted can mismatch the correct one. This ends up being rejected later in the legacy paths (should be the only path currently used), but we should reject it here (or in principle we could reject it after cache lookup, but we are fixing up the operation DTypes here anyway, so we are looking at the signature). A call sequence reproducing this directly is: np.add(1, 2, signature=(bool, int, None)) # should fail np.add(True, 2) # A promoted loop np.add(1, 2, signature=(bool, int, None)) # should still fail Not that the errors differ, because the first one comes from the old type resolution code and is currently less precise --- numpy/core/src/umath/dispatching.c | 9 +++++++-- numpy/core/tests/test_ufunc.py | 9 +++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/numpy/core/src/umath/dispatching.c b/numpy/core/src/umath/dispatching.c index 5aecdd1fc942..79de6c3c85bc 100644 --- a/numpy/core/src/umath/dispatching.c +++ b/numpy/core/src/umath/dispatching.c @@ -1016,8 +1016,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 e4b1ceee3e2f..ce30e63dbe0c 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.