-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
WIP: Caller location for macros #20895
Conversation
|
+1. See also #13339 |
Ah excellent, thanks @vtjnash for pointing that out. I found about four other issues mentioning this problem, including an old PR from Keno, but missed #13339. Not sure why, perhaps I just missed it in the github search. I'll open a new PR for this, with an actual description and discussion of the possible design variations, unless people would prefer me just to reopen this one. |
less noise to keep using this one. just be sure to reopen before pushing anything else to the branch or github might prevent it from being reopened |
Sure, no problems, I've reopened it, this time with a proper description. |
Hmm, test failures seem unrelated, cf. #20906 |
base/loading.jl
Outdated
Return the line number where the current macro expansion was invoked. | ||
""" | ||
function current_location() | ||
convert(Int, ccall(:jl_current_lineno, Cint, ())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than making this thread local, shouldn't this just be Int(unsafe_load(cglobal(:jl_lineno, Cint)))
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, but good question. ptls->current_lineno
is not really a replacement for jl_lineno
, but rather a place to stash the line number of the macro caller. (The only place it's set is inside fl_invoke_julia_macro
.) As such I've probably named it badly, what if I rename it as macro_caller_lineno
or something?
Side question - is there an approved way to load thread local state from julia without creating a new C function specifically for the purpose? I didn't see one.
I went ahead and put the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the parens in @__LINE__() + 1
necessary after this change in order to not parse it as @__LINE__(+,1)
or @__LINE__(+1)
? Did this PR change that parsing?
I'm also unclear on what it means to include the location information in @__LINE__
. The tests and docs on the PR currently don't clarify it for me.
What I did in the last two patches was remove the |
@StefanKarpinski I've updated the description to hopefully be a bit clearer, and added some tests at the top of test/loading.jl for the behavior implemented so far. I'm not a fan of the fact I had to update the |
Exciting, would be great to get this issue resolved!
|
Great question, I should add a test case for this. The behavior implemented here is that macro a()
quote
@__LINE__
end
end
macro b()
line_a_called_inside_b = @__LINE__() + 2
quote
(@a(), $line_a_called_inside_b)
end
end
@show @b
# Which shows
@b() = (12, 12) With changes to the Expanding only a single level won't be the right thing in all circumstances, but it works well for macros which occur inside other macros taking an entire scope ( I don't have a feel for performance impact yet, needs some testing I guess. I'm not quite sure that |
Tests now refactored for clarity, with cases added to test the expansion of An interesting benefit of expanding |
Any more comments on this one? Is it clear enough what the use cases for these changes are? I think this addresses the core of what's needed for #9577, #9579 and #13339? I'm keen to move along implementing any further changes people think necessary to turn this from a WIP into something ready for production. Things I'm planning to do:
|
d9aa9dc
to
82f8471
Compare
Implement __LOCATION__ macro to give macros access to the line number in the context they were called. This is expanded during the variable resolution step after each macro is executed. * Ensure that line number and filename information is preserved in the AST when running from the REPL, as the REPL expands and evals the AST in a separate task from where it's parsed. This allows current_location() to work properly at the REPL.
Here we pass the line number (tracked inside macroexpand.scm) into fl_invoke_julia_macro and stash it on the thread local storage, from which it can be retrieved inside a macro using the current_location() function. This is a bit ugly but gets the job done - it seems like the only alternative would be to change the macro calling convention to insert an implicit context parameter (an ABI change only, but much more disruptive).
Fix test cases to reflect the addition of line number information at top level in the infrastructure used by parse_input_line. As part of this, ensure the filename is passed as a symbol in the line number nodes.
__LINE__ now expands to the caller location when used inside a macro. In principle this is a breaking change, but having __LINE__ expand to the macro AST location seems fairly useless and surprising in general. Especially for people coming from C where the new behaviour will be more familiar.
Now that __LINE__ parses like a normal macro, it sometimes needs parens to avoid unexpectedly catching parameters.
Plus some refactoring to improve the test case naming.
82f8471
to
b44c155
Compare
Replaced |
Thanks c42f for the PR. I appreciated the perspective on the alternate approach, and used your tests in my PR. |
Ah, it's frustrating that github doesn't send notifications when issues are linked. Otherwise I'd have reviewed #21746 a month ago when it was created.. |
By the way guys, if it was obvious that I was going in completely the wrong direction with this one it would have been good to know a little sooner. Just a quick and clear "Hey, don't go too far down the wrong path, I really think we're going to solve this with the approach in #13339" would have been good. Now that I've dived into the system, it's much easier to appreciate the way the new parser and expansion step from #13339 work together to pass the necessary information without breaking user code. So I learned a lot and this wasn't wasted time, but perhaps could have been spent a little more fruitfully elsewhere, with just a little nudge ;-) |
@c42f sorry for lack of further comment on my part. I didn't like the single-level limitation, and that made the approach "feel" brittle, but I wasn't sure I fully understood the approach (nor could take the time to do at the point when I was commenting). I decided to leave constructive feedback because it was still an improvement! |
That's ok, luckily I'm not too attached to the particular implementation details - really I just want the problem solved. It's a little disappointing to see a bit of the effort go to waste, but mainly I'm happy that the problem is mostly solved by #21746.
But this is constructive feedback! "I don't like this PR because of 'X'" is a perfectly valid thing to say - it opens the discussion about why things were implemented the way they are, or sheds light on why the approach is bad or good. I don't think there's anything single level about what I did here, by the way (it supports arbitrary nesting of expansions, and allows macros to control whether they're visible to |
Luckily it looks like I'm wrong about this. In which case, awesome, I'm quite happy with what's in master. It's breakage in the AST layout which could have been avoided, but with the upside of being more explicit, which seems like a good balance. |
As discussed in #9577 (see also #9579 and #13339), sometimes it would be really useful to have the line number and file of the location a macro was called. Personally I ran across a related problem trying to make a prototype for a new logging system (wip at JuliaLang/Juleps#30, see also c42f/MicroLogging.jl#1) where I want the source line of each log message to be extracted at compilation time. The current version of
@__LINE__
doesn't do what we want here, as it's expanded immediately by the parser and gives the line number of the macro body rather than the calling code.In this WIP,
I've implemented aI've moved the behavior of the@__LOCATION__
macro, which is expanded to the line of the calling code with a rule at macro expansion time. This uses the newly available line numbers in the AST so I think it's fairly non disruptive@__LINE__
macro from the parser to macro expansion time, and expanded it to the line of code where the macro was called from, when inside a macro expansion. As a side effect, the special parsing of@__LINE__
was dropped, which means it parses the same as every other macro. The same machinery also allows us to solve #9577 in a non disruptive way by adding the line number of the macro caller to thread local state in a way that macros can get at macro expansion time without changing the macro calling convention. So I just went ahead and did that too.demo.jl:
Now for the WIP bit - I'd love some feedback on the design and implementation here:
Design
I'm not happy with having three distinct mechanisms for talking about lines (ie,
@__LINE__
,@__LOCATION__
, andcurrent_location()
).I'm inclined to tweak this so thatdone. Also naming bikeshedding, etc. I'm not attached to any naming used here.@__LINE__
takes over the@__LOCATION__
functionality, as emitting@__LINE__
inside a macro AST is currently not very usefulAs you see above, the REPL still gives
@__FILE__
asnothing
, despite the parser seeing a filename ofREPL[N]
. I'd like to refactor@__FILE__
to use the same mechanism that I'm using for@__LOCATION__
here - is this reasonable? I'm confused by the current method by which@__FILE__
is expanded.Implementation
Despite reading a fair bit of code, I still don't think I understand the various mechanisms by which the global
jl_lineno
may be set and reset. Especially given the various code paths by which statements are parsed and executed in the repl vs from file vs whatever else. I'm very inclined to refactor things by removing the global entirely, and forcing all line/filename information to flow via the ASTs, but that's for another PR, I think (advice very welcome).I'm not quite sure how reasonable my use of
with-bindings
is or whether it would be preferable to passcurrent-lineno
explicitly (@ihnorton can I join the scheme riff-raff club? :-) )Oh, by the way @JeffBezanson, having this in at least three languages is... a strangely winning mixture of fun and frustration :-)