Use of eval in closed module and "incremental compilation may be broken"

In Package A we would like to offer facilities to write metadata about models of a specific type that would be written in other packages, say B.

To do this we have something like this

In Package A:

module A
input_type(T) = nothing
function meta(T, input_type)
  ex = quote
    input_type(::Type{<:$T}) = $input_type
  end
  eval(ex)
end
end

in Package B:

module B
import A: meta
export NewModel
struct NewModel end
meta(NewModel, Real)
end

The expected output being that

julia> using A, B
julia> input_type(B)
Real

(Incidentally if you do this in the REPL replacing import A with import Main.A it seems to work fine).

However in my context A and B are two separate packages and doing the steps above can lead to the following warning when precompiling B:

using B
[ Info: Precompiling B [...]
WARNING: eval into closed module A:
Expr(:block, #= Symbol( ...

resulting in a warning incremental compilation may be broken for this module. I suspect this is due to a poor use of eval and issues with where/when things are evaluated but it’s unclear to me how to fix the issue.

There’s no need to use eval() here: Julia’s normal method dispatch already handles this situation perfectly:

julia> module A
         export input_type
         
         input_type(T) = nothing
       end
WARNING: replacing module A.
Main.A

julia> module B
         import ..A: input_type
         export NewModel
         
         struct NewModel end
         input_type(::Type{NewModel}) = Real
       end
WARNING: replacing module B.
Main.B

julia> A.input_type(B.NewModel)
Real

Yes I’m aware this could be accomplished by multiple dispatch and I apologise that my example is a bit too simplistic and encourages this answer.

Let’s say I really want to use eval here because I don’t want to call input_type in module B (effectively there’s a series of such functions from A that I want to be wrapped in that function meta to reduce code duplication when possible).

Maybe to clarify, I’d like A to expose a function meta that can assign a number of traits, something like

function meta(T, ....)
  trait_function1(T, ...)
  trait_function2(T, ...)
end

If I do this without quote I get other errors like “static parameters cannot be used in closure” so this is why I went for eval.

I’m still pretty sure eval() is the wrong tool here. What about a macro that expands to the code you actually want to generate? This seems like a perfect use-case for a macro.

1 Like

Yeah I guess I should try with a macro, I’ll report here if I manage to get that working, thanks. (and I agree that eval is likely not the best tool).

So if anyone should read this question in the future, I finally found out that the problem was to specify properly where the eval happens (and yes you actually have to use eval here because the emitted code has to be from A not B)

Anyway so calling parentmodule(T).eval solves the problem, it took me some time to find it but it works perfectly for my use case :sweat_smile:

1 Like