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

RFC: rewrite_sourceloc! function #22623

Merged
merged 2 commits into from
Oct 30, 2020
Merged

Conversation

c42f
Copy link
Member

@c42f c42f commented Jun 29, 2017

A tool to propagate the implicit __source__ variable from a macro to any
macro calls in the generated AST.

This automates @vtjnash's trick from #21746 (comment) and makes it accessible to people who want to do the common task of making uses of @__LINE__ inside a macro expand to the location of the macro caller, rather than a location inside the macro. (The trick is simple to use directly in some cases, but this tool makes it a lot easier to use if @__LINE__ is deeply embeeded in the AST.)

An obvious question is whether this should propagate into nested macro calls when processing the AST. For now, I've not done this but perhaps I need to think a bit more about that.

base/loading.jl Outdated
function propagate_sourceloc(sourceloc, ex)
if ex isa Expr
if ex.head == :macrocall
ex.args[2] = sourceloc
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it, I'd better recurse into this as well.

base/loading.jl Outdated
"""
macro propagate_sourceloc(ex)
:(propagate_sourceloc($(esc(:__source__)), $(esc(ex))))
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This macro feels unnecessary to me - using the function form alone seems cleaner.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. How will we deal with 0.6 compatibility?

For context, I ported this utility from here: https://github.com/c42f/MicroLogging.jl/blob/e37dffcb7aeb9c581a27b897fa9d4fe3467d8b85/src/util.jl#L34 where I'm trying to support both 0.6 and 0.7; in 0.6 the __source__ variable isn't present which is the reason for the macro.

What's the sanest way to deal with getting a nop version of this into Compat? I could add something to make the following work in 0.6, by just removing the function call. A bit messy!

@compat propagate_sourceloc(__source__, quote
    @__LINE__
end)

Alternatively I could just move the @propagate_sourceloc macro to Compat...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about:

code = quote end
Compat.macros_have_sourceloc && propagate_sourceloc!(__source__, code)
return code
```

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok then. The few people who care about this (myself and two others?) can delete the small amount of mess once 0.7 is out - no need to make it super complicated.

@c42f
Copy link
Member Author

c42f commented Jun 30, 2017

I changed some things as suggested and added some documentation.

@@ -50,6 +50,7 @@ Base.Filesystem.basename
Base.@__FILE__
Base.@__DIR__
Base.@__LINE__
Base.propagate_sourceloc!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't seem like the right manual section for this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, however this is most related to @__LINE__, @__FILE__ and @__DIR__. Perhaps all these should be moved to another location. The location in the manual ("metaprogramming") seems much more sensible for these things, as they're all about introspection of the macro expansion process.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about moving all four of these to "doc/src/stdlib/base.md"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved these around - hopefully into a more coherent location in the stdlib docs.

base/exports.jl Outdated
@@ -1214,6 +1214,7 @@ export
@__FILE__,
@__DIR__,
@__LINE__,
propagate_sourceloc!,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this need to be exported?

Copy link
Member Author

@c42f c42f Jul 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. That depends on whether there's a distinction between "public API" and "exported function". I want this as a tool which can be used by packages, but it's also relatively obscure. So if there's a well defined category "public but not exported" this would fit in there, and we could remove the export.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some things that are public but not exported, usually it's whether they are documented in a public way as if to be used qualified. Most of those are under more specific modules than Base though, like Profile. Could maybe move this to the Meta module?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let's try this and see how it looks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, so if it's in Meta it probably shouldn't be re-exported from Base too

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes. I could have sworn I fixed that.

julia> dump(@__LOCATION__())
Tuple{Int64,String}
1: Int64 1
2: String "REPL[2]"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this pass when you run the doctests?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will now ;-)

@c42f
Copy link
Member Author

c42f commented Jul 1, 2017

Now moved to Base.Meta and exported from there. There seemed to be a couple of useful exported but non documented functions in Base.Meta so I had a go at documenting them while I was at it.

@c42f
Copy link
Member Author

c42f commented Jul 2, 2017

Looks like the travis failure is a timeout.

@ihnorton
Copy link
Member

ihnorton commented Jul 2, 2017

Why base?

base/meta.jl Outdated
macro example()
propagate_sourceloc!(__source__,
quote
@__LINE__
Copy link
Member

@ihnorton ihnorton Jul 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm not understanding the purpose of this example, so I'm wary of, erhm, propagating it... If it's trying to solve c42f/MicroLogging.jl@f6db371#commitcomment-20846418 then why not interpolate __source__.line? @__LINE__ is documented and implemented as an almost-literal (that's why we need the parser to insert it the value in the macrocall), so I think having this would be confusing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is somewhat related to that old comment, but addresses the more general problem of wrapping macros which make use of __source__, for example here:

https://github.com/c42f/MicroLogging.jl/blob/e37dffcb7aeb9c581a27b897fa9d4fe3467d8b85/src/MicroLogging.jl#L205

If it were just @__LINE__ I was worried about, interpolating __source__.line would be a better solution.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is, if package A provides the macro

macro a()
    quote
        some_complicated_expression($(__source__.line))
    end
end

And package B wishes to wrap it -

macro b()
    quote
        # ...
        @a
    end
end

while preserving reporting of the source location, you either need a tool a bit like this, or to manually dig into the AST generated in @b and rewrite the source location of the macro invocations contained within.

@c42f
Copy link
Member Author

c42f commented Jul 2, 2017

Why base?

Because without this small tool, using @__LINE__ and @__FILE__ inside a macro expands to the location inside the macro, which is almost never what you want. So in this sense, it belongs in the standard library, to make some other core features of Base usable.

@ihnorton
Copy link
Member

ihnorton commented Jul 5, 2017

Someone else should probably comment/approve. My thoughts are:

  • not clear on why this needs to be recursive vs. a single-shot forwarding call like in the discussion thread with Jameson.
  • I would prefer not exporting, and removing the example, at least from the narrative Metaprogramming section. Macros-calling-macros is fairly advanced and I don't think that such an example should be in the intro docs (at some point we may need to split some content off to an "Advanced Metaprogramming" section). Many issues such as this can be avoided by making the code generator a function.

@ihnorton
Copy link
Member

ihnorton commented Jul 5, 2017

It would be great to find a more general way to handle this issue, e.g. during expansion, but given that AFAICT Clojure hasn't solved it either, I suspect it will be challenging.

Also closely related: http://tratt.net/laurie/blog/entries/debugging_layers

@c42f c42f changed the title RFC: propagate_sourceloc macro to expand __LINE__ in caller context RFC: propagate_sourceloc function Nov 28, 2017
@c42f
Copy link
Member Author

c42f commented Nov 28, 2017

I've just wanted this again, so I've had occasion to come back to this and clean it up as suggested (the function is exported by Meta, but not by Base, so I've left that alone).

As to whether this needs to be recursive - well obviously it doesn't, but it's a lot nicer for it to be recursive when the macro call or calls are nested deeply in the AST.

I fully agree that having shared functions which generate Exprs for use in multiple macros is generally nicer. This is a good idea if you have control over both macros. But when the called macro is from a separate module this isn't so easy.

@StefanKarpinski
Copy link
Member

The functionality 👍, that name though... Of course, I don't have any better ideas.

@c42f
Copy link
Member Author

c42f commented Nov 29, 2017

Yes, the name is now quite a lot worse than it was when this PR was first submitted. I think I'll change the name to replace_sourceloc! instead which is a lot more descriptive.

(Regarding length, both names are long but I think that's desirable for obscure and little used functionality.)

@c42f
Copy link
Member Author

c42f commented Nov 29, 2017

Renamed. I had a think, but I'm all out of names which describe this operation clearly but succinctly. replace_sourceloc is at least consistent with Compat.macros_have_sourceloc.

@c42f c42f changed the title RFC: propagate_sourceloc function RFC: rewrite_sourceloc function Nov 29, 2017
@c42f c42f changed the title RFC: rewrite_sourceloc function RFC: rewrite_sourceloc! function Nov 29, 2017
@c42f
Copy link
Member Author

c42f commented Nov 29, 2017

I assume the OSX Travis failures are spurious. It's hard to imagine how they could be related to the changes here.

@KristofferC
Copy link
Member

KristofferC commented Nov 29, 2017

Could this be used to fix the following:

julia> @test_nowarn warn("fds")
Test Failed at /Users/kristoffer/julia/usr/share/julia/site/v0.7/Test/src/Test.jl:521

Here the source location info from the @test that is wrapped inside the @test_nowarn macro and we thus print the "wrong" location. We really want the location where @test_nowarn was expanded.

@c42f
Copy link
Member Author

c42f commented Nov 29, 2017

Yes, this is exactly what this function is for.

(Though I'm just about to make @test_warn obsolete, I hope!)

@vtjnash vtjnash added the forget me not PRs that one wants to make sure aren't forgotten label Oct 27, 2020
@vtjnash
Copy link
Member

vtjnash commented Oct 27, 2020

Anyone remember: why didn't we ever merge this?

A tool to propagate the __source__ variable from a macro to any
macro calls in a given AST such that the inner macro calls are
attributed the same line and file name.
Also find an improved location for __FILE__, __DIR__ and __LINE__ in the
manual.
@c42f
Copy link
Member Author

c42f commented Oct 30, 2020

I don't know why this wasn't merged; I think mainly the discussion went on long enough that I eventually forgot about it.

Thanks for rebasing. I still use this trick occasionally, I just tend to write it out by hand.

@vtjnash vtjnash merged commit 10b0a01 into JuliaLang:master Oct 30, 2020
@simeonschaub simeonschaub removed the forget me not PRs that one wants to make sure aren't forgotten label May 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
macros @macros
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants