For the initial latency, could you use Revise & CodeTracking if they are loaded? Demo:
f(x::Int) = x + 1
f(x::AbstractFloat) = x + 2
g(y) = "hey there"
computex(::String) = 0
computex(::Dict) = 0.0
function myfunction(d::AbstractDict, key)
x = computex(d)
y = f(x)
return g(y)
end
function myfunction(d::AbstractString, key)
x = computex(d)
y = f(x)
return g(y)
end
If the user’s cursor is on line 19, and you want the function signature, you can do this:
julia> using CodeTracking, Revise
julia> includet("/tmp/lsdemo.jl") # Revise has to parse the file to cache signatures
julia> sigs = signatures_at("/tmp/lsdemo.jl", 19)
1-element Array{Any,1}:
Tuple{typeof(myfunction),AbstractString,Any}
Once you have the signature you should be able to query the documentation.
Revise’s signatures test script has more tricks that might be interesting. The highlighted lines in this link extract the signatures from Base; the line at the bottom is proof that it failed on fewer than 40 signatures (out of the >11000 in Base). The ones it misses tend to be tricky, like +(::OrdinalRange, ::OrdinalRange)
which is defined by an @eval
inside a function body. If the @eval
had been at top-level it would have figured it out, even for some fairly tricky cases:
julia> signatures_at("julia/base/atomics.jl", 422)
4-element Array{Any,1}:
Tuple{typeof(Base.Threads.atomic_add!),Base.Threads.Atomic{T},T} where T<:Union{Float16, Float32, Float64}
Tuple{typeof(Base.Threads.atomic_sub!),Base.Threads.Atomic{T},T} where T<:Union{Float16, Float32, Float64}
Tuple{typeof(Base.Threads.atomic_max!),Base.Threads.Atomic{T},T} where T<:Union{Float16, Float32, Float64}
Tuple{typeof(Base.Threads.atomic_min!),Base.Threads.Atomic{T},T} where T<:Union{Float16, Float32, Float64}
which is pretty fun when you see how arcanely these methods are defined.
But you can go farther than this. In this demo, F12 on f
doesn’t take you to the correct definition of f
—LanguageServer sometimes struggles (for understandable reasons) with multiple dispatch. I’m not saying it’s easy or will always be successful, but in some cases there is a way to figure it out. First we have to figure out what argument type f
will have, for which we’ll use inference:
julia> sig = sigs[1];
julia> meth = Revise.JuliaInterpreter.whichtt(sig)
myfunction(d::AbstractString, key) in Main at /tmp/lsdemo.jl:18
julia> src = Core.Compiler.typeinf_code(meth, sig, Core.svec(), false, Core.Compiler.Params(typemax(UInt)))
(CodeInfo(
@ /tmp/lsdemo.jl:18 within `myfunction'
1 ─ (x = Main.computex(d))::Core.Compiler.Const(0, false)
│ @ /tmp/lsdemo.jl:19 within `myfunction'
│ (y = Main.f(x::Core.Compiler.Const(0, false)))::Core.Compiler.Const(1, false)
│ @ /tmp/lsdemo.jl:20 within `myfunction'
│ %3 = Main.g(y::Core.Compiler.Const(1, false))::Core.Compiler.Const("hey there", false)
└── return %3
), String)
julia> src = src[1];
julia> src.code[2] # the second entry in `src.code` has the call to `f`
:(_5 = Main.f(_4::Core.Compiler.Const(0, false)))
julia> src.slottypes[4] # _4 indicates a "slot" and so we can ask what type it has
Int64
OK, so in this case we can fortuitously determine that f
will be called with an Int64
. (This would not have been possible if the signature of this method of myfunction
had not specified AbstractString
, so there will be many cases where this kind of lookup will fail.) From there, problem solved:
julia> which(f, (Int64,))
f(x::Int64) in Main at /tmp/lsdemo.jl:1