From fc0d62795a2228afd442703324e29ed10898061a 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 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): From fb9666ff84dbbd1c0f802795399c57db54eeab83 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 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.