Confusing MethodError: bad message or bad usage?

The MethodError message sometimes doesn’t make sense, and I’m not sure if it’s a problem with MethodError or the way it’s used.

For example the following fails (as documented in Measurements.jl):

julia> using SpecialFunctions, Measurements

julia> zeta(2.0 ± 0.1)
ERROR: MethodError: no method matching zeta(::Measurement{Float64})
Closest candidates are:
  zeta(::Number) at /home/user/.julia/packages/SpecialFunctions/Bdhxh/src/gamma.jl:527
  zeta(::Number, ::Number) at /home/user/.julia/packages/SpecialFunctions/Bdhxh/src/gamma.jl:547
  zeta(::BigFloat) at /home/user/.julia/packages/SpecialFunctions/Bdhxh/src/gamma.jl:465
  ...

But note how the message is confusing, since there is a candidate zeta(::Number) and

julia> Measurement{Float64} <: Number
true

The error comes from the following code in SpecialFunctions.jl:

 function $f(z::Number) 
     x = float(z) 
     typeof(x) === typeof(z) && throw(MethodError($f, (z,))) 
     # There is nothing to fallback to, as this didn't change the argument types 
     $f(x) 
 end

My question: Is this an issue with Julia (MethodError printing a message that assumes too much), or is it a semantically incorrect use of MethodError?

Similar cases in the standard library

Sometimes ArgumentError is used for example in base/abstractarray.jl:

require_one_based_indexing(A...) = !has_offset_axes(A...) || throw(ArgumentError("offset arrays are not supported but got an array with index other than 1"))

and

checkindex(::Type{Bool}, inds::AbstractUnitRange, i) =
    throw(ArgumentError("unable to check bounds for indices of type $(typeof(i))"))

These are helpful messages.

But in other places, MethodError is used and leads to the same problem. For example in base/irrationals.jl:

<(::Irrational{s}, ::Irrational{s}) where {s} = false
function <(x::AbstractIrrational, y::AbstractIrrational)
    Float64(x) != Float64(y) || throw(MethodError(<, (x, y)))
    return Float64(x) < Float64(y)
end

And in base/essentials.jl:

function tuple_type_tail(T::Type)
  ...
        T.name === Tuple.name || throw(MethodError(tuple_type_tail, (T,)))
  ...
end

This last one gives the following message, similarly confusing:

julia> Base.tuple_type_tail(Int32)
ERROR: MethodError: no method matching tuple_type_tail(::Type{Int32})
Closest candidates are:
  tuple_type_tail(::Type) at essentials.jl:211
Stacktrace:
 [1] tuple_type_tail(::Type) at ./essentials.jl:217
 [2] top-level scope at REPL[56]:1

So, bad message or bad use of MethodError?

1 Like