diff --git a/stdlib/SparseArrays/src/higherorderfns.jl b/stdlib/SparseArrays/src/higherorderfns.jl index e09ca104c42208..63d318af434f90 100644 --- a/stdlib/SparseArrays/src/higherorderfns.jl +++ b/stdlib/SparseArrays/src/higherorderfns.jl @@ -973,6 +973,7 @@ function _copy(f, args...) parevalf, passedargstup = capturescalars(f, args) return _copy(parevalf, passedargstup...) end +_copy(f) = throw(MethodError(_copy, (f,))) # avoid method ambiguity function _shapecheckbc(f, args...) _aresameshape(args...) ? _noshapecheck_map(f, args...) : _diffshape_broadcast(f, args...) @@ -1006,10 +1007,6 @@ end _copyto!(parevalf, dest, passedsrcargstup...) end -struct CapturedScalars{F, Args, Order} - args::Args -end - # capturescalars takes a function (f) and a tuple of mixed sparse vectors/matrices and # broadcast scalar arguments (mixedargs), and returns a function (parevalf, i.e. partially # evaluated f) and a reduced argument tuple (passedargstup) containing only the sparse @@ -1024,9 +1021,13 @@ end # Work around losing Type{T}s as DataTypes within the tuple that makeargs creates @inline capturescalars(f, mixedargs::Tuple{Ref{Type{T}}, Vararg{Any}}) where {T} = capturescalars((args...)->f(T, args...), Base.tail(mixedargs)) +@inline capturescalars(f, mixedargs::Tuple{Ref{Type{T}}, Ref{Type{S}}, Vararg{Any}}) where {T, S} = + # This definition is identical to the one above and necessary only for + # avoiding method ambiguity. + capturescalars((args...)->f(T, args...), Base.tail(mixedargs)) @inline capturescalars(f, mixedargs::Tuple{SparseVecOrMat, Ref{Type{T}}, Vararg{Any}}) where {T} = capturescalars((a1, args...)->f(a1, T, args...), (mixedargs[1], Base.tail(Base.tail(mixedargs))...)) -@inline capturescalars(f, mixedargs::Tuple{Union{Ref,AbstractArray{0}}, Ref{Type{T}}, Vararg{Any}}) where {T} = +@inline capturescalars(f, mixedargs::Tuple{Union{Ref,AbstractArray{<:Any,0}}, Ref{Type{T}}, Vararg{Any}}) where {T} = capturescalars((args...)->f(mixedargs[1], T, args...), Base.tail(Base.tail(mixedargs))) nonscalararg(::SparseVecOrMat) = true diff --git a/stdlib/SparseArrays/test/ambiguous_exec.jl b/stdlib/SparseArrays/test/ambiguous_exec.jl new file mode 100644 index 00000000000000..a466f2534794a6 --- /dev/null +++ b/stdlib/SparseArrays/test/ambiguous_exec.jl @@ -0,0 +1,4 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +using Test, SparseArrays +@test detect_ambiguities(SparseArrays; imported=true, recursive=true) == [] diff --git a/stdlib/SparseArrays/test/higherorderfns.jl b/stdlib/SparseArrays/test/higherorderfns.jl index 5871e2d96e1e23..5b1423582f0d54 100644 --- a/stdlib/SparseArrays/test/higherorderfns.jl +++ b/stdlib/SparseArrays/test/higherorderfns.jl @@ -632,4 +632,28 @@ end @test minimum(sparse([1, 2], [1, 2], ones(Int32, 2)), dims = 1) isa Matrix end +@testset "Issue #30118" begin + @test ((_, x) -> x).(Int, spzeros(3)) == spzeros(3) + @test ((_, _, x) -> x).(Int, Int, spzeros(3)) == spzeros(3) + @test ((_, _, _, x) -> x).(Int, Int, Int, spzeros(3)) == spzeros(3) + @test_broken ((_, _, _, _, x) -> x).(Int, Int, Int, Int, spzeros(3)) == spzeros(3) +end + +using SparseArrays.HigherOrderFns: SparseVecStyle + +@testset "Issue #30120: method ambiguity" begin + # HigherOrderFns._copy(f) was ambiguous. It may be impossible to + # invoke this from dot notation and it is an error anyway. But + # when someone invokes it by accident, we want it to produce a + # meaningful error. + err = try + copy(Broadcast.Broadcasted{SparseVecStyle}(rand, ())) + catch err + err + end + @test err isa MethodError + @test !occursin("is ambiguous", sprint(showerror, err)) + @test occursin("no method matching _copy(::typeof(rand))", sprint(showerror, err)) +end + end # module diff --git a/stdlib/SparseArrays/test/sparse.jl b/stdlib/SparseArrays/test/sparse.jl index 27cbbe947bcbc8..d82fac08b6a1e5 100644 --- a/stdlib/SparseArrays/test/sparse.jl +++ b/stdlib/SparseArrays/test/sparse.jl @@ -2341,4 +2341,12 @@ end @test m2.module == SparseArrays end +@testset "method ambiguity" begin + # Ambiguity test is run inside a clean process. + # https://github.com/JuliaLang/julia/issues/28804 + script = joinpath(@__DIR__, "ambiguous_exec.jl") + cmd = `$(Base.julia_cmd()) --startup-file=no $script` + @test success(pipeline(cmd; stdout=stdout, stderr=stderr)) +end + end # module