From 621a62e85c3d9d8861598fc4d2aa81e426ba31e0 Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Tue, 13 Oct 2026 03:02:43 +0000 Subject: [PATCH 1/2] Throw from `any` and `all` of a sparse array on a non-Boolean value, as dense does Co-Authored-By: Claude Opus 5.5 --- src/reductions.jl | 9 +++++++-- src/sparsevector.jl | 4 ++-- test/reductions.jl | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/reductions.jl b/src/reductions.jl index 3930d243..4a80375f 100644 --- a/src/reductions.jl +++ b/src/reductions.jl @@ -93,10 +93,15 @@ _mapreducezeros(f::Base.ExtremaMap, op::typeof(Base._extrema_rf), ::Type{T}, nze nzeros == 0 ? v0 : op(v0, f(zero(T))) # Specialized mapreduce for any and all +# Each value of the predicate is checked to be Boolean, as the dense reductions check it: +# `|` and `&` would absorb another value next to a `missing`, or combine two of them. +_boolean(v::Union{Bool,Missing}) = v +_boolean(v) = v::Bool +_booleanpredicate(f::F) where {F} = _boolean ∘ f Base._any(f, A::SparseMatrixCSCOrColumnSubset, ::Colon) = - iszero(widelength(A)) ? false : Base._mapreduce(f, |, IndexCartesian(), A) + iszero(widelength(A)) ? false : Base._mapreduce(_booleanpredicate(f), |, IndexCartesian(), A) Base._all(f, A::SparseMatrixCSCOrColumnSubset, ::Colon) = - iszero(widelength(A)) ? true : Base._mapreduce(f, &, IndexCartesian(), A) + iszero(widelength(A)) ? true : Base._mapreduce(_booleanpredicate(f), &, IndexCartesian(), A) function Base._mapreduce(f::F, op::Union{typeof(Base.mul_prod),typeof(*)}, ::Base.IndexCartesian, A::SparseMatrixCSCOrColumnSubset{T}) where {F,T} nnzA = nnz(A) diff --git a/src/sparsevector.jl b/src/sparsevector.jl index b56a3d0f..ad424ebb 100644 --- a/src/sparsevector.jl +++ b/src/sparsevector.jl @@ -1525,9 +1525,9 @@ function Base._mapreduce(f::F, op::G, ::IndexCartesian, A::SparseVectorOrView) w end Base._any(f, A::SparseVectorOrView, ::Colon) = - iszero(length(A)) ? false : Base._mapreduce(f, |, IndexCartesian(), A) + iszero(length(A)) ? false : Base._mapreduce(_booleanpredicate(f), |, IndexCartesian(), A) Base._all(f, A::SparseVectorOrView, ::Colon) = - iszero(length(A)) ? true : Base._mapreduce(f, &, IndexCartesian(), A) + iszero(length(A)) ? true : Base._mapreduce(_booleanpredicate(f), &, IndexCartesian(), A) function Base.mapreducedim!(f::F, op::G, R::AbstractVector, A::SparseVectorOrView) where {F,G} isempty(A) && return R diff --git a/test/reductions.jl b/test/reductions.jl index 24f310da..7eef1c74 100644 --- a/test/reductions.jl +++ b/test/reductions.jl @@ -127,6 +127,25 @@ along(f::F, X, dims) where {F} = f(X; dims) @test count(v) == length(v) @test all(!iszero, spzeros(0, 0)) @test !any(iszero, spzeros(0, 0)) + @static if COMPREHENSIVE + # a predicate that is not Boolean is rejected, as for dense + for S in (sparse([1 0; 0 0]), sparsevec([1, 0])) + @test_throws TypeError any(S) + @test_throws TypeError all(S) + end + # also next to a `missing`, which `|` and `&` would let absorb the other value + for S in (sparse(reshape(Union{Missing,Int}[missing, 1], 2, 1)), sparse(Union{Missing,Int}[missing, 1]), + sparse(Union{Missing,Int}[missing 0; 1 2])) + @test_throws TypeError any(S) + @test_throws TypeError all(S) + @test_throws TypeError any(Array(S)) + @test_throws TypeError all(Array(S)) + end + M = sparse(Union{Missing,Bool}[missing false; false false]) + @test any(M) === missing && all(M) === false + @test any(iszero, sparse([1.0 0; 2 3])) === true && all(iszero, sparse([1.0 0; 2 3])) === false + @test any(ismissing, M) === true && all(x -> x === false, sparsevec([false, false])) === true + end end @testset "seed without copying the first slice" begin From 62ecbde227fb3a5759bcde33f5ad79d4b6769eb6 Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Tue, 13 Oct 2026 03:18:09 +0000 Subject: [PATCH 2/2] Trim the `any`/`all` tests to one case per path Co-Authored-By: Claude Opus 5.5 --- test/reductions.jl | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/test/reductions.jl b/test/reductions.jl index 7eef1c74..122fdf0e 100644 --- a/test/reductions.jl +++ b/test/reductions.jl @@ -134,17 +134,9 @@ along(f::F, X, dims) where {F} = f(X; dims) @test_throws TypeError all(S) end # also next to a `missing`, which `|` and `&` would let absorb the other value - for S in (sparse(reshape(Union{Missing,Int}[missing, 1], 2, 1)), sparse(Union{Missing,Int}[missing, 1]), - sparse(Union{Missing,Int}[missing 0; 1 2])) - @test_throws TypeError any(S) - @test_throws TypeError all(S) - @test_throws TypeError any(Array(S)) - @test_throws TypeError all(Array(S)) - end - M = sparse(Union{Missing,Bool}[missing false; false false]) - @test any(M) === missing && all(M) === false - @test any(iszero, sparse([1.0 0; 2 3])) === true && all(iszero, sparse([1.0 0; 2 3])) === false - @test any(ismissing, M) === true && all(x -> x === false, sparsevec([false, false])) === true + S = sparse(reshape(Union{Missing,Int}[missing, 1], 2, 1)) + @test_throws TypeError any(S) + @test_throws TypeError all(S) end end