Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there a way to wrap the functional form of @infiltrate into a macro? #94

Closed
sloede opened this issue May 15, 2023 · 6 comments
Closed

Comments

@sloede
Copy link

sloede commented May 15, 2023

For the life of me, I cannot remember how to type

if isdefined(Main, :Infiltrator)
  Main.infiltrate(@__MODULE__, Base.@locals, @__FILE__, @__LINE__)
end

Since I think Infiltrator.jl is a great tool for debugging and we like to use it when working with Trixi.jl, I thought about adding a macro to Trixi.jl such as

macro trixi_infiltrate(cond = true)
  quote
    if $(esc(cond)) && isdefined($(esc(Main)), :Infiltrator)
      $(esc(Main)).infiltrate($(__module__), Base.@locals, $(String(__source__.file)), $(__source__.line))
    end
  end
end

However, while this works functionally, it will produce the wrong information about where infiltrate(...) was called - I assume it's because the macro adds another stackframe and thus

start = something(findlast(x -> x.func === Symbol("start_prompt"), trace), 0) + 2

should be a + 3 (or a + 1?) instead of + 2.

Is there a way around this limitation, i.e., could I make it produce the correct stack frame by doing something different in my macro definition? Or is this just an inherent limitation and would require Infiltrator.jl itself to be changed?

@pfitzseb
Copy link
Member

pfitzseb commented May 15, 2023

macro infiltrate′(cond = true)
    pkgid = Base.PkgId(Base.UUID("5903a43b-9cc3-4c30-8d17-598619ec4e9b"), "Infiltrator")
    if !haskey(Base.loaded_modules, pkgid)
        try
            Base.eval(Main, :(using Infiltrator))
        catch err
            @error "Cannot load Infiltrator.jl. Make sure it is included in your environment stack."
        end
    end
    i = get(Base.loaded_modules, pkgid, nothing)
    lnn = LineNumberNode(__source__.line, __source__.file)

    if i === nothing
        return Expr(
            :macrocall,
            Symbol("@warn"),
            lnn,
            "Could not load Infiltrator.")
    end

    return Expr(
        :macrocall,
        Expr(:., i, QuoteNode(Symbol("@infiltrate"))),
        lnn,
        cond
    )
end 

seems to work fairly reliably and conveniently:

julia> module Foo
       f(x) = 2*g(x)
       g(x) = begin
         y = 2x
         Main.@infiltrate′
       end
       end
WARNING: replacing module Foo.
Main.Foo

julia> h(x) = Foo.g(cos(x))
h (generic function with 1 method)

julia> h(2)
Infiltrating g
  at REPL[12]:5

infil> @trace
[1] g
    at REPL[12]:5
[2] h(x::Int64)
    at REPL[13]:1
[3] top-level scope

I'll include this macro in the readme/docs.

@pfitzseb
Copy link
Member

@sloede
Copy link
Author

sloede commented May 15, 2023

Great, thanks a lot for the quick & helpful reply 🙏

@sloede
Copy link
Author

sloede commented May 15, 2023

Further testing revealed that there seems to be something broken with either how I use this or in the implementation:

If I copy paste https://github.com/JuliaDebug/Infiltrator.jl#auto-loading-infiltratorjl to the REPL and define

function foo(condition)
  @autoinfiltrate condition
end

then running foo(x) with any argument x will result in the following error:

julia> foo(true)
ERROR: UndefVarError: `condition` not defined
Stacktrace:
   [1] macro expansion
     @ ~/.julia/packages/Infiltrator/r3Hf5/src/Infiltrator.jl:61 [inlined]
   [2] foo(cond::Bool)
     @ Main ./REPL[23]:2
   [3] top-level scope
     @ REPL[25]:1

I think the issue here is that the argument gets expanded to Main.arg, while it should just be arg, since the error goes away if I do

julia> condition = true
true

julia> foo(true)
Infiltrating foo(condition::Bool)
  at REPL[28]:2

infil>

and as evidenced by

julia> bar(condition) = @macroexpand @autoinfiltrate condition
bar (generic function with 3 methods)

julia> bar(true)
quote
    #= /home/mschlott/.julia/packages/Infiltrator/r3Hf5/src/Infiltrator.jl:61 =#
    if Main.condition
        #= /home/mschlott/.julia/packages/Infiltrator/r3Hf5/src/Infiltrator.jl:62 =#
        (Infiltrator.start_prompt)(Main, $(Expr(:locals)), "REPL[41]", 1)
    end
end

Unfortunately I am way out of my depth regarding metaprogramming here, but maybe you know how to fix this?

@pfitzseb
Copy link
Member

Ah, yeah, I missed an esc there. The code is updated now, so give it another go.

@sloede
Copy link
Author

sloede commented May 15, 2023

Thanks, that seemed to do it 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants