Logically, almost all the confusion you got comes from over generalizing everything. Things “work” under specific constraints and tweaks on those constraints that seem minor for you can be (and are) fundamantal.
All what I’m saying is that the enclosing local scope is not something you could access at macro expansion time by design. It is related to micro hygene since that’s exactly about how to access variables in the macro generated code in the correct scope, i.e. it is the section that talks about how to “access the enclosing local scope” at macro run time (i.e. in the code generated by the macro). In this case, you must not generalize something you could do for global scope to something you could do to local scope. And that’s not just an API limitation, it’s fundamantal in so many ways.
Again, don’t overgeneralize what I said and also please don’t make major change to the code between the two posts that invalidate my post. The code I was replying to, the only foo
from the short github thread, was,
using Test
@testset begin #This works fine without @testset
function foo()
0
end
println(myMod.@client)
end
If you haven’t realized that yet, moving something from inside an include
to directly at the calling site completely changed the scope it runs in (there are many threads on this and I’ll perfer not talking about it in this thread). This might be a fundamantal change in the constraints that you didn’t realize. And as for why I said you can’t access foo
in this case (i.e. the original case on github), that is still absolutely true. But that is only about when you define it in a local scope (@testset
scope) for the reason that “It’s not even created when your macro is called.” as I said in the github post. More on that below.
So now about two things you definitely need to learn that I didn’t want to post on github.
-
First thing is the one you constantly misinterpreting what I said about foo
and looking for all signs/execuses/chances to call me wrong on it.
Again, all what I said is the foo
on github, a function defined in a local scope, is impossible to access during macro expansion time. There’s absolutely no comment from other people you’ve seen in this thread that conflicts that and there won’t be any from anyone that knows what they are saying…
And now there’s the why.
First, macros are transformations on the code. Only after macro expansion of the code it could be run. This is done for each global/toplevel expression. This is the smallest unit one can run the code/interleave macro expansion and execution. The macro expansion result could alter how a global statement could be run so it cannot be done before the macro expansion finishes. What this means is that if you have
begin
foo() = ...
@your_macro
end
@your_macro
is called before any part of this block of code is evaluated. I hope it’s easy to understand that if no part of this code has run, the foo() = ...
would not have been evaluated, and there’s no foo
for you to use anywhere at that time.
Next, local scope. You cannot access a local scope externally. This is fundamental to the design that worth a separate thread if you have question about it. There are certainly some internal data structure you can use to do something close in certain conditions but they are implementation details and messing with them comes at your own risk both for crashing and for breaking your code. Local scope also means that things in it does not exist until the scope is created, which is hopefully easy to see. Of course the object might exist if they don’t need to be created but by all mean there’s no way to access them with the name in that scope.
-
The second one is about what you thin you saw in the following code you posted on github:
julia> code_lowered(bar)
1-element Array{Core.CodeInfo,1}:
CodeInfo(
1 ─ barbar = %new(Main.:(#barbar#10))
│ %2 = (barbar)()
└── return %2
)
I assume you saw the Main.:(#barbar#10)
and thought
So it does seems that at least nested functions have the declaration as some kind of field.
Well, Main.:(#barbar#10)
is the type of the function. It is not the function itself. barbar
, i.e. %new(Main.:(#barbar#10))
is the function. In general you cannot go from the type directly to the value. Even though it actually is in this case since the function is not a closure (it doesn’t enclose/capture any local variable) that type is not yet defined when your macro is called and just the name is not even enough to get the type you need.
And now for what you should/could do for your “real code”. If you just claim it works by accessing the module at macro expansion time and it’s only the test issue then you have the following constraints.
- Your macro can only access global functions. There’s no way you can access local functions.
- The function defined before your macro is called. Since that’s when you are doing the work, there’s absolutely no way around it.
And your fix would be simply to NOT define any local function to use in the test. In another word, move your foo
out of the @testset
and all enclosing top-level code blocks and you’ll be fine.
The only way to get around these liminations is to not do the work at macro expansion time. You need to figure out a way to do it at runtime. Depending on what you want to do with the function, this may or may not be possible.
Finally, just because a package that does something like this exists does not mean it should be done or is the right thing to do or is the right way to do it. Please don’t use that as an argument for anything. There are so much code out there that has caused trouble that I really wish would not be there (to be fair, this one seems to be pretty mild on the overall scale). In this case, just because it works in some cases does not mean it should work in other cases. Thare are some liminations put in by design and you should not try to do certain things (reflection for example) at the wrong time.