Updating the location of a method definition

Is there a good way to change where Julia thinks a method definition is located (file and line number)? I got halfway there with this:

m = first(methods(MyModule.mymethod))
m.file = Symbol("/path/to/real/file")
m.line = real_line

(I can find the correct method definition when methods returns more than one, in this example first is just a simplification.)

This works well enough for uses internally reliant on methods for file and line information, but does not work for displayed stack traces. Stack traces still show the old location of the method definition.

The following fails because LineInfoNode is an immutable struct:

m = first(methods(MyModule.mymethod))
Base.uncompressed_ast(m).linetable[1].file = Symbol("/path/to/real/file")

Is there any way to force Julia to see a different location for a redefined function?

Context: I wrote a Julia IDE for Emacs. One of its features allows redefinition of a function without reloading an entire file. This messes up cross-referencing (which I figured out how to fix) and stack traces (which I have not). Exact bug: https://github.com/gcv/julia-snail/issues/28.

Pinging @tim.holy, @longemen3000, and @tkf who have helped me with questions about Julia internals in the past. :upside_down_face:

If you control how the code is evaluated, maybe you can just use task_local_storage()[:SOURCE_PATH]? For example, see how Distributed.@everywhere pass around “current file context”:

https://github.com/JuliaLang/julia/blob/bdacfa21ef7d9657be0ff3220947ceca92a340a1/stdlib/Distributed/src/macros.jl#L205

It’s not enough to make line positions correct, though. For this, I think it’s simpler to parse the code and change the LineNumberNode yourself before eval. Or, maybe even simpler solution to add empty lines in the temp file.

1 Like

Load CodeTracking and Revise and then use whereis.

2 Likes

Changing LineNumberNodes worked perfectly. Thank you.

The problem here is imagine inserting a blank line at the top of a file with 1000 methods. Do you really want to re-eval all the methods just to update the line numbers? That’s going to force a lot of invalidation and recompilation.

Much better to use the CodeTracking + Revise strategy, which is completely free of recompilation.

1 Like

@tim.holy: Indeed, but Snail does not attempt to update all affected line numbers. It only changes LineNumberNode instances for the code being directly evaluated (a top-level form or selected region) before calling eval on it. While this messes up line numbering for all surrounding code, this tends to happen with SLIME and CIDER equivalent commands also (or at least it used to). Re-evaluating the buffer with julia-snail-send-buffer-file will fix everything (albeit with invalidation). I consider this acceptable for this type of tool.

That said, it’s great that a Revise-oriented workflow won’t have any of these drawbacks. Snail users are welcome to use Revise, avoid julia-snail-send-region or julia-snail-send-top-level-form, and still enjoy Snail’s autocompletion and cross-referencing features. But, I just tested a Revise-oriented workflow, and ran into an interesting problem.

My test involved just making a simple Julia source file with a sample function samplefun. Then I called Revise.includet on the source file. Revise started working on that file, as expected. But, I found that calling CodeTracking.whereis(@which samplefun()) gives me correct output only when called from the REPL, and not when called through Snail’s network interface. If I move samplefun around by adding blank lines, then CodeTracking.whereis returns stale locations when called through Snail’s network interface, but correct locations when called from the REPL.

Using functionloc on a method to determine its location exhibits the same behavior: correct from the REPL, wrong when invoked outside the REPL.

Do you know anything about this? Does Julia or Revise maintain some kind of internal source location cache which is invalidated only when these introspection functions are called from the REPL?

If you use Emacs, I can talk you through reproducing the behavior pretty easily. If you don’t use Emacs, it’ll take a bit more work, but I can write a handful of netcat commands that use Snail’s network backend to demonstrate the behavior.

That’s because Revise modifies the REPL to call revise() before evaluating what the user types.

Perfect. Thanks! I now have Revise compatibility working in Snail as expected.

1 Like