I thought the answer was no, since hasmethod
is not pure. But unless I try very hard to break it (see struct B
example below) I cannot invoke any suspicious bugs. From the example I wrote, it seems that using hasmethod
inside @generated
function is OK as long as the method checked by hasmethod
is already defined before the first invocation of the method. Is it correct? Are there any other pitfalls? Am I relying on some implementation details of julia
which can change in the future?
To avoid the XY problem here is the context: What I need in the end is a single type-stable Base.setindex
-like method that works with immutable tuples/arrays such as StaticArrays and also mutable arrays such as standard Array
. I thought setindex_static
below would be the straightforward implementation if hasmethod
were pure (but if hasmethod
were pure we can’t program anything in Julia).
I wonder adding world age bounds to CodeInfo would solve this kind of issue.
module TestHasMethodHack
using Test
function setindex_on_copy(obj, val, indices...)
clone = similar(obj, promote_type(eltype(obj), typeof(val)))
copyto!(clone, obj)
setindex!(clone, val, indices...)
return clone
end
struct A
x
end
Base.setindex(::A, x, ::Integer) = A(x)
@generated function setindex_static(obj, val, indices...)
if hasmethod(Base.setindex, Tuple{obj, val, indices...})
setter = Base.setindex
else
setter = setindex_on_copy
end
quote
$setter(obj, val, indices...)
end
end
@assert setindex_static([0], 1, 1) == [1] # Having it here (or commenting it out) does not change anything.
struct B
x
end
try
setindex_static(B(1), 2, 1)
catch err
@info "Ignoring" exception=err
end
Base.setindex(::B, x, ::Integer) = B(x)
struct C
x
end
Base.setindex(::C, x, ::Integer) = C(x)
@testset begin
@test setindex_static(A(0), 1, 1) == A(1)
@test_broken setindex_static(B(0), 1, 1) == B(1)
@test setindex_static(C(0), 1, 1) == C(1)
end
end # module
which outputs:
┌ Info: Ignoring
│ exception =
│ MethodError: no method matching similar(::Main.TestHasMethodHack.B, ::Type{Any})
│ Closest candidates are:
│ similar(::Array{T,1}, ::Type) where T at array.jl:329
│ similar(::Array{T,2}, ::Type) where T at array.jl:330
│ similar(::Array, ::Type, ::Tuple{Vararg{Int64,N}}) where N at array.jl:332
â”” ...
Test Summary: | Pass Broken Total
test set | 2 1 3