As per my edit, the solution is to check a flag in the relevant show method. A lot of digging later (and also my first time using Debugger.jl, which was awesome, by the way), I found out this can be done by modifying this line in Base.
It’s quite a long function so I’m hiding it, but the diff between this and Base is 3 lines (and could technically be just one).
Summary
@eval Base function show_tuple_as_call(io::IO, name::Symbol, sig::Type, demangle=false, kwargs=nothing)
# print a method signature tuple for a lambda definition
in_backtrace = get(io, :backtrace, false)
color = get(io, :color, false) && in_backtrace ? stackframe_function_color() : :nothing
if sig === Tuple
printstyled(io, demangle ? demangle_function_name(name) : name, "(...)", color=color)
return
end
tv = Any[]
env_io = io
while isa(sig, UnionAll)
push!(tv, sig.var)
env_io = IOContext(env_io, :unionall_env => sig.var)
sig = sig.body
end
sig = sig.parameters
with_output_color(color, env_io) do io
ft = sig[1]
uw = unwrap_unionall(ft)
if ft <: Function && isa(uw, DataType) && isempty(uw.parameters) &&
isdefined(uw.name.module, uw.name.mt.name) &&
ft == typeof(getfield(uw.name.module, uw.name.mt.name))
print(io, (demangle ? demangle_function_name : identity)(uw.name.mt.name))
elseif isa(ft, DataType) && ft.name === Type.body.name && !Core.Compiler.has_free_typevars(ft)
f = ft.parameters[1]
print(io, f)
else
print(io, "(::", ft, ")")
end
end
first = true
print_style = get(io, :color, false) && in_backtrace ? :bold : :nothing
printstyled(io, "(", color=print_style)
for i = 2:length(sig) # fixme (iter): `eachindex` with offset?
first || print(io, ", ")
first = false
print(env_io, "::", in_backtrace ? typename(sig[i]) : sig[i])
end
if kwargs !== nothing
print(io, "; ")
first = true
for (k, t) in kwargs
first || print(io, ", ")
first = false
print(io, k, "::")
show(io, t)
end
end
printstyled(io, ")", color=print_style)
show_method_params(io, tv)
nothing
end
For the effect, see:
## Create a contrived stacktrace to see the effect
## and a similar method for h so we get "Closest candidates"
julia> begin
f(x) = g(x, x, x)
g(x, y, z) = h(x, y, z)
h(x::Complicated{Float64}, y::Complicated{Float64}) = "anything"
end
julia> struct Complicated{a,b,c,d} end
julia> C = Complicated{ntuple(i->Float64, 4)...}()
Complicated{Float64,Float64,Float64,Float64}()
julia> f(C)
ERROR: MethodError: no method matching h(::Complicated{Float64,Float64,Float64,Float64}, ::Complicated{Float64,Float64,Float64,Float64}, ::Complicated{Float64,Float64,Float64,Float64})
Closest candidates are:
h(::Complicated{Float64,b,c,d} where d where c where b, ::Complicated{Float64,b,c,d} where d where c where b) at REPL[16]:1
Stacktrace:
[1] g(::Complicated, ::Complicated, ::Complicated) at ./REPL[16]:1
[2] f(::Complicated) at ./REPL[16]:1
[3] top-level scope at REPL[19]:1
We get the full method signature in the method error itself and in the closest candidates (which we may need to know to disambiguate the error), but the type name in the stacktrace is always shortened. It’s still worth considering I think whether this is behavior that should be enabled by default, or whether only certain “complicated” types should feature this, i.e. if this is opt-in by the struct creator.