Help with macros and calling a macro from a different module

I’m very new to macros so any help is appreciated.
Currently I want to extend JuMP and there inside a macro this line exists:

buildcall = :(_build_indicator_constraint($_error, $(esc(variable)), $rhs_buildcall, $S))

where _build_indicator_constraint exists in JuMP but if I change it to _build_my_constraint and write the function in my module it seems like that JuMP when actually calling buildcall does not know about the function because it is in my module.

Hope that makes sense. Is there any way around this problem?

1 Like

You can try to use @macroexpand to find out what the final representation of all these variables is in the generated expression. I’ve had problems with proper escaping before and this helped me to track down which variables needed module namespacing and which didn’t.

I think the rule is that any function used in a macro is namespaced to the module where the macro was defined. So if you write do_this(x) in a macro that’s defined in MyModule it’s as if you had written MyModule.do_this wherever you run this macro.

If you don’t want that, but want whatever do_this to be called that happens to be valid in the place where the macro is called, you need to escape do_this.

In your case, it might be that you want the opposite behavior? I’m not sure if I understand correctly what your precise issue is. There are two options:

Either the name that is not found is in your module but it was used escaped in the macro. Then unescape it.

Or the name that is not found is in the calling module but is being looked for in your module because it’s not escaped. Then escape it.

I hope that made sense, macros are tricky!

2 Likes

Thanks for the answer. I’m not entirely sure how to do what you propose but using @macroexpand seems to help me.

Currently the last line of @macroexpand reads something like this:

 JuMP.add_constraint(m, JuMP._build_my_constraint(JuMP.var"#_error#68"{Symbol}(Core.Box(Any[:m, :($(Expr(:(:=), :a, :({x + y <= 1}))))]), :constraint), a, JuMP.build_constraint(JuMP.var"#_error#68"{Symbol}(Core.Box(Any[:m, :($(Expr(:(:=), :a, :({x + y <= 1}))))]), :constraint), JuMP._functionize(var"#220###418"), MathOptInterface.LessThan{Float64}(0.0)), ..., "")

So the problem is the JuMP._build_my_constraint it should be _build_my_constraint as far as I understand.

I’m struggling with using esc in a way that only the function name gets escaped and not the function arguments.

i.e I can produce:

JuMP.add_constraint(m, _build_my_constraint(JuMP.var"#_error#68"{Symbol}(Core.Box(Any[:m, :($(Expr(:(:=), :a, :({x + y <= 1}))))]), :constraint), $(Expr(:escape, :a)), build_constraint(JuMP.var"#_error#68"{Symbol}(Core.Box(Any[:m, :($(Expr(:(:=), :a, :({x + y <= 1}))))]), :constraint), _functionize(var"##510"), $(Expr(:escape, MathOptInterface.LessThan{Float64}(0.0)))), ..., "")

but there everything is escaped because I do escape everything :laughing:

esc(:(_build_my_constraint($_error, $(esc(variable)), $rhs_buildcall, $S)))

I need to unescape the arguments now? If so how to do that?

Does this explain it? Here is a function called in a macro, where only the function symbol is escaped (I left out all arguments for clarity):

macro test()
    quote
        $(esc(:dothis))()
    end
end

julia> @macroexpand @test()
quote
    dothis()
end

The resulting expression just calls dothis wherever it is used.

On the other hand, if we don’t escape the function symbol, it’s scoped to Main, where I defined the macro:

macro test2()
    quote
        $(:dothis)()
    end
end

julia> @macroexpand @test2()
quote
    Main.dothis()
end

The second version is a convoluted way of writing dothis() directly, but I hope it makes the contrast to the first version clearer.

2 Likes

Thanks I think it works now. This is the version now:

buildcall = :($(esc(:(CS._build_my_constraint)))($_error, $(esc(variable)), $rhs_buildcall, $S))

So I also had to add CS. for my module because otherwise it’s probably looking in the current scope which is the global one where that is not defined.

I messed up my parenthesis before which caused errors but your solution works and makes sense. Thanks again!