Skip to content

Commit f2dcc44

Browse files
committed
Remove unspecialized reduce_empty
The fallback definitions of `reduce_empty` and `mapreduce_empty` are big targets for invalidation, and the benefit they bring is questionable. Instead of having a dedicated error path, instead we print a custom `MethodError` hint that is more informative than the current message. This fixes ~500 invalidations from DiffEqBase
1 parent a03392a commit f2dcc44

File tree

9 files changed

+73
-42
lines changed

9 files changed

+73
-42
lines changed

base/errorshow.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ function showerror(io::IO, ex::MethodError)
226226
return showerror_ambiguous(io, meth, f, arg_types)
227227
end
228228
arg_types_param::SimpleVector = arg_types.parameters
229+
show_candidates = true
229230
print(io, "MethodError: ")
230231
ft = typeof(f)
231232
name = ft.name.mt.name
@@ -242,6 +243,9 @@ function showerror(io::IO, ex::MethodError)
242243
if f === Base.convert && length(arg_types_param) == 2 && !is_arg_types
243244
f_is_function = true
244245
show_convert_error(io, ex, arg_types_param)
246+
elseif f === mapreduce_empty || f === reduce_empty
247+
print(io, "reducing over an empty collection is not allowed; consider supplying `init` to the reducer")
248+
show_candidates = false
245249
elseif isempty(methods(f)) && isa(f, DataType) && isabstracttype(f)
246250
print(io, "no constructors have been defined for ", f)
247251
elseif isempty(methods(f)) && !isa(f, Function) && !isa(f, Type)
@@ -314,7 +318,7 @@ function showerror(io::IO, ex::MethodError)
314318
end
315319
end
316320
Experimental.show_error_hints(io, ex, arg_types_param, kwargs)
317-
try
321+
show_candidates && try
318322
show_method_candidates(io, ex, kwargs)
319323
catch ex
320324
@error "Error showing method candidates, aborted" exception=ex,catch_backtrace()

base/reduce.jl

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -299,30 +299,42 @@ pairwise_blocksize(::typeof(abs2), ::typeof(+)) = 4096
299299

300300
# handling empty arrays
301301
_empty_reduce_error() = throw(ArgumentError("reducing over an empty collection is not allowed"))
302+
_empty_reduce_error(@nospecialize(f), @nospecialize(T::Type)) = throw(ArgumentError("""
303+
reducing with $f over an empty collection of element type $T is not allowed.
304+
You may be able to prevent this error by supplying an `init` value to the reducer."""))
302305

303306
"""
304307
Base.reduce_empty(op, T)
305308
306309
The value to be returned when calling [`reduce`](@ref), [`foldl`](@ref) or [`foldr`](@ref)
307310
with reduction `op` over an empty array with element type of `T`.
308311
309-
If not defined, this will throw an `ArgumentError`.
312+
This should only be defined in unambiguous cases; for example,
313+
314+
```julia
315+
Base.reduce_empty(::typeof(+), ::Type{T}) where T = zero(T)
316+
```
317+
318+
is justified (the sum of zero elements is zero), whereas
319+
`reduce_empty(::typeof(max), ::Type{Any})` is not (the maximum value of an empty collection
320+
is generally ambiguous, and especially so when the element type is unknown).
321+
322+
As an alternative, consider supplying an `init` value to the reducer.
310323
"""
311-
reduce_empty(op, ::Type{T}) where {T} = _empty_reduce_error()
312-
reduce_empty(::typeof(+), ::Type{Union{}}) = _empty_reduce_error()
324+
reduce_empty(::typeof(+), ::Type{Union{}}) = _empty_reduce_error(+, Union{})
313325
reduce_empty(::typeof(+), ::Type{T}) where {T} = zero(T)
314326
reduce_empty(::typeof(+), ::Type{Bool}) = zero(Int)
315-
reduce_empty(::typeof(*), ::Type{Union{}}) = _empty_reduce_error()
327+
reduce_empty(::typeof(*), ::Type{Union{}}) = _empty_reduce_error(*, Union{})
316328
reduce_empty(::typeof(*), ::Type{T}) where {T} = one(T)
317329
reduce_empty(::typeof(*), ::Type{<:AbstractChar}) = ""
318330
reduce_empty(::typeof(&), ::Type{Bool}) = true
319331
reduce_empty(::typeof(|), ::Type{Bool}) = false
320332

321-
reduce_empty(::typeof(add_sum), ::Type{Union{}}) = _empty_reduce_error()
333+
reduce_empty(::typeof(add_sum), ::Type{Union{}}) = _empty_reduce_error(add_sum, Union{})
322334
reduce_empty(::typeof(add_sum), ::Type{T}) where {T} = reduce_empty(+, T)
323335
reduce_empty(::typeof(add_sum), ::Type{T}) where {T<:SmallSigned} = zero(Int)
324336
reduce_empty(::typeof(add_sum), ::Type{T}) where {T<:SmallUnsigned} = zero(UInt)
325-
reduce_empty(::typeof(mul_prod), ::Type{Union{}}) = _empty_reduce_error()
337+
reduce_empty(::typeof(mul_prod), ::Type{Union{}}) = _empty_reduce_error(mul_prod, Union{})
326338
reduce_empty(::typeof(mul_prod), ::Type{T}) where {T} = reduce_empty(*, T)
327339
reduce_empty(::typeof(mul_prod), ::Type{T}) where {T<:SmallSigned} = one(Int)
328340
reduce_empty(::typeof(mul_prod), ::Type{T}) where {T<:SmallUnsigned} = one(UInt)
@@ -337,11 +349,8 @@ reduce_empty(op::FlipArgs, ::Type{T}) where {T} = reduce_empty(op.f, T)
337349
338350
The value to be returned when calling [`mapreduce`](@ref), [`mapfoldl`](@ref`) or
339351
[`mapfoldr`](@ref) with map `f` and reduction `op` over an empty array with element type
340-
of `T`.
341-
342-
If not defined, this will throw an `ArgumentError`.
352+
of `T`. See [`Base.reduce_empty`](@ref) for more information.
343353
"""
344-
mapreduce_empty(f, op, T) = _empty_reduce_error()
345354
mapreduce_empty(::typeof(identity), op, T) = reduce_empty(op, T)
346355
mapreduce_empty(::typeof(abs), op, T) = abs(reduce_empty(op, T))
347356
mapreduce_empty(::typeof(abs2), op, T) = abs2(reduce_empty(op, T))
@@ -355,7 +364,10 @@ mapreduce_empty_iter(f, op, itr, ItrEltype) =
355364

356365
@inline reduce_empty_iter(op, itr) = reduce_empty_iter(op, itr, IteratorEltype(itr))
357366
@inline reduce_empty_iter(op, itr, ::HasEltype) = reduce_empty(op, eltype(itr))
358-
reduce_empty_iter(op, itr, ::EltypeUnknown) = _empty_reduce_error()
367+
reduce_empty_iter(op, itr, ::EltypeUnknown) = throw(ArgumentError("""
368+
reducing over an empty collection of unknown element type is not allowed.
369+
You may be able to prevent this error by supplying an `init` value to the reducer."""))
370+
359371

360372
# handling of single-element iterators
361373
"""
@@ -726,7 +738,7 @@ julia> maximum([1,2,3])
726738
3
727739
728740
julia> maximum(())
729-
ERROR: ArgumentError: reducing over an empty collection is not allowed
741+
ERROR: MethodError: reducing over an empty collection is not allowed; consider supplying `init` to the reducer
730742
Stacktrace:
731743
[...]
732744
@@ -758,7 +770,7 @@ julia> minimum([1,2,3])
758770
1
759771
760772
julia> minimum([])
761-
ERROR: ArgumentError: reducing over an empty collection is not allowed
773+
ERROR: MethodError: reducing over an empty collection is not allowed; consider supplying `init` to the reducer
762774
Stacktrace:
763775
[...]
764776

stdlib/SparseArrays/test/sparse.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,8 @@ end
786786
end
787787

788788
@testset "empty cases" begin
789+
errchecker(str) = occursin("reducing over an empty collection is not allowed", str) ||
790+
occursin("collection slices must be non-empty", str)
789791
@test sum(sparse(Int[])) === 0
790792
@test prod(sparse(Int[])) === 1
791793
@test_throws ArgumentError minimum(sparse(Int[]))
@@ -798,9 +800,9 @@ end
798800
@test isequal(f(spzeros(0, 1), dims=3), f(Matrix{Int}(I, 0, 1), dims=3))
799801
end
800802
for f in (minimum, maximum, findmin, findmax)
801-
@test_throws ArgumentError f(spzeros(0, 1), dims=1)
803+
@test_throws errchecker f(spzeros(0, 1), dims=1)
802804
@test isequal(f(spzeros(0, 1), dims=2), f(Matrix{Int}(I, 0, 1), dims=2))
803-
@test_throws ArgumentError f(spzeros(0, 1), dims=(1, 2))
805+
@test_throws errchecker f(spzeros(0, 1), dims=(1, 2))
804806
@test isequal(f(spzeros(0, 1), dims=3), f(Matrix{Int}(I, 0, 1), dims=3))
805807
end
806808
end

test/abstractarray.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1433,7 +1433,8 @@ using Base: typed_hvncat
14331433
v1 == v2 == 1 && continue
14341434
for v3 ((), (1,), ([1],), (1, [1]), ([1], 1), ([1], [1]))
14351435
@test_throws ArgumentError hvncat((v1, v2), true, v3...)
1436-
@test_throws ArgumentError hvncat(((v1,), (v2,)), true, v3...)
1436+
@test_throws str->(occursin("`shape` argument must consist of positive integers", str) ||
1437+
occursin("reducing over an empty collection is not allowed", str)) hvncat(((v1,), (v2,)), true, v3...)
14371438
end
14381439
end
14391440
end

test/missing.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -465,10 +465,10 @@ end
465465
@test_throws BoundsError x[3, 1]
466466
@test findfirst(==(2), x) === nothing
467467
@test isempty(findall(==(2), x))
468-
@test_throws ArgumentError argmin(x)
469-
@test_throws ArgumentError findmin(x)
470-
@test_throws ArgumentError argmax(x)
471-
@test_throws ArgumentError findmax(x)
468+
@test_throws "reducing over an empty collection is not allowed" argmin(x)
469+
@test_throws "reducing over an empty collection is not allowed" findmin(x)
470+
@test_throws "reducing over an empty collection is not allowed" argmax(x)
471+
@test_throws "reducing over an empty collection is not allowed" findmax(x)
472472
end
473473
end
474474

@@ -525,8 +525,8 @@ end
525525
for n in 0:3
526526
itr = skipmissing(Vector{Union{Int,Missing}}(fill(missing, n)))
527527
@test sum(itr) == reduce(+, itr) == mapreduce(identity, +, itr) === 0
528-
@test_throws ArgumentError reduce(x -> x/2, itr)
529-
@test_throws ArgumentError mapreduce(x -> x/2, +, itr)
528+
@test_throws "reducing over an empty collection is not allowed" reduce(x -> x/2, itr)
529+
@test_throws "reducing over an empty collection is not allowed" mapreduce(x -> x/2, +, itr)
530530
end
531531

532532
# issue #35504

test/reduce.jl

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ end
4949
@test reduce(max, [8 6 7 5 3 0 9]) == 9
5050
@test reduce(+, 1:5; init=1000) == (1000 + 1 + 2 + 3 + 4 + 5)
5151
@test reduce(+, 1) == 1
52-
@test_throws ArgumentError reduce(*, ())
53-
@test_throws ArgumentError reduce(*, Union{}[])
52+
@test_throws "reducing with * over an empty collection of element type Union{} is not allowed" reduce(*, ())
53+
@test_throws "reducing with * over an empty collection of element type Union{} is not allowed" reduce(*, Union{}[])
5454

5555
# mapreduce
5656
@test mapreduce(-, +, [-10 -9 -3]) == ((10 + 9) + 3)
@@ -87,8 +87,10 @@ end
8787
@test mapreduce(abs2, *, Float64[]) === 1.0
8888
@test mapreduce(abs2, max, Float64[]) === 0.0
8989
@test mapreduce(abs, max, Float64[]) === 0.0
90-
@test_throws ArgumentError mapreduce(abs2, &, Float64[])
91-
@test_throws ArgumentError mapreduce(abs2, |, Float64[])
90+
@test_throws ["reducing over an empty collection is not allowed",
91+
"consider supplying `init`"] mapreduce(abs2, &, Float64[])
92+
@test_throws str -> !occursin("Closest candidates are", str) mapreduce(abs2, &, Float64[])
93+
@test_throws "reducing over an empty collection is not allowed" mapreduce(abs2, |, Float64[])
9294

9395
# mapreduce() type stability
9496
@test typeof(mapreduce(*, +, Int8[10])) ===
@@ -138,8 +140,9 @@ fz = float(z)
138140
@test sum(z) === 136
139141
@test sum(fz) === 136.0
140142

141-
@test_throws ArgumentError sum(Union{}[])
142-
@test_throws ArgumentError sum(sin, Int[])
143+
@test_throws "reducing with add_sum over an empty collection of element type Union{} is not allowed" sum(Union{}[])
144+
@test_throws ["reducing over an empty collection is not allowed",
145+
"consider supplying `init`"] sum(sin, Int[])
143146
@test sum(sin, 3) == sin(3.0)
144147
@test sum(sin, [3]) == sin(3.0)
145148
a = sum(sin, z)
@@ -170,7 +173,7 @@ for f in (sum2, sum5, sum6, sum9, sum10)
170173
end
171174
for f in (sum3, sum4, sum7, sum8)
172175
@test sum(z) == f(z)
173-
@test_throws ArgumentError f(Int[])
176+
@test_throws "reducing over an empty" f(Int[])
174177
@test sum(Int[7]) == f(Int[7]) == 7
175178
end
176179
@test typeof(sum(Int8[])) == typeof(sum(Int8[1])) == typeof(sum(Int8[1 7]))
@@ -239,8 +242,8 @@ prod2(itr) = invoke(prod, Tuple{Any}, itr)
239242

240243
# maximum & minimum & extrema
241244

242-
@test_throws ArgumentError maximum(Int[])
243-
@test_throws ArgumentError minimum(Int[])
245+
@test_throws "reducing over an empty" maximum(Int[])
246+
@test_throws "reducing over an empty" minimum(Int[])
244247

245248
@test maximum(Int[]; init=-1) == -1
246249
@test minimum(Int[]; init=-1) == -1
@@ -594,14 +597,22 @@ end
594597
# issue #18695
595598
test18695(r) = sum( t^2 for t in r )
596599
@test @inferred(test18695([1.0,2.0,3.0,4.0])) == 30.0
597-
@test_throws ArgumentError test18695(Any[])
600+
@test_throws str -> ( occursin("reducing over an empty", str) &&
601+
occursin("consider supplying `init`", str) &&
602+
!occursin("or defining", str)) test18695(Any[])
603+
604+
# For Core.IntrinsicFunction
605+
@test_throws str -> ( occursin("reducing over an empty", str) &&
606+
occursin("consider supplying `init`", str) &&
607+
!occursin("or defining", str)) reduce(Base.xor_int, Int[])
598608

599609
# issue #21107
600610
@test foldr(-,2:2) == 2
601611

602612
# test neutral element not picked incorrectly for &, |
603613
@test @inferred(foldl(&, Int[1])) === 1
604-
@test_throws ArgumentError foldl(&, Int[])
614+
@test_throws ["reducing over an empty",
615+
"consider supplying `init`"] foldl(&, Int[])
605616

606617
# prod on Chars
607618
@test prod(Char[]) == ""

test/reducedim.jl

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ end
9090

9191
# Combining dims and init
9292
A = Array{Int}(undef, 0, 3)
93-
@test_throws ArgumentError maximum(A; dims=1)
93+
@test_throws "reducing over an empty collection is not allowed" maximum(A; dims=1)
9494
@test maximum(A; dims=1, init=-1) == reshape([-1,-1,-1], 1, 3)
9595

9696
# Test reduction along first dimension; this is special-cased for
@@ -169,8 +169,9 @@ end
169169
A = Matrix{Int}(undef, 0,1)
170170
@test sum(A) === 0
171171
@test prod(A) === 1
172-
@test_throws ArgumentError minimum(A)
173-
@test_throws ArgumentError maximum(A)
172+
@test_throws ["reducing over an empty",
173+
"consider supplying `init`"] minimum(A)
174+
@test_throws "consider supplying `init`" maximum(A)
174175

175176
@test isequal(sum(A, dims=1), zeros(Int, 1, 1))
176177
@test isequal(sum(A, dims=2), zeros(Int, 0, 1))
@@ -182,9 +183,9 @@ end
182183
@test isequal(prod(A, dims=3), fill(1, 0, 1))
183184

184185
for f in (minimum, maximum)
185-
@test_throws ArgumentError f(A, dims=1)
186+
@test_throws "reducing over an empty collection is not allowed" f(A, dims=1)
186187
@test isequal(f(A, dims=2), zeros(Int, 0, 1))
187-
@test_throws ArgumentError f(A, dims=(1, 2))
188+
@test_throws "reducing over an empty collection is not allowed" f(A, dims=(1, 2))
188189
@test isequal(f(A, dims=3), zeros(Int, 0, 1))
189190
end
190191
for f in (findmin, findmax)

test/spawn.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -579,8 +579,8 @@ end
579579
@test_throws ArgumentError run(Base.AndCmds(`$truecmd`, ``))
580580

581581
# tests for reducing over collection of Cmd
582-
@test_throws ArgumentError reduce(&, Base.AbstractCmd[])
583-
@test_throws ArgumentError reduce(&, Base.Cmd[])
582+
@test_throws "reducing over an empty collection is not allowed" reduce(&, Base.AbstractCmd[])
583+
@test_throws "reducing over an empty collection is not allowed" reduce(&, Base.Cmd[])
584584
@test reduce(&, [`$echocmd abc`, `$echocmd def`, `$echocmd hij`]) == `$echocmd abc` & `$echocmd def` & `$echocmd hij`
585585

586586
# readlines(::Cmd), accidentally broken in #20203

test/tuple.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ end
283283
@test mapfoldl(abs, =>, (-1,-2,-3,-4), init=-10) == ((((-10=>1)=>2)=>3)=>4)
284284
@test mapfoldl(abs, =>, (), init=-10) == -10
285285
@test mapfoldl(abs, Pair{Any,Any}, (-30:-1...,)) == mapfoldl(abs, Pair{Any,Any}, [-30:-1...,])
286-
@test_throws ArgumentError mapfoldl(abs, =>, ())
286+
@test_throws "reducing over an empty collection" mapfoldl(abs, =>, ())
287287
end
288288

289289
@testset "filter" begin

0 commit comments

Comments
 (0)