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

add builtin @__FUNCTION__ macros #6733

Open
srp opened this issue May 3, 2014 · 13 comments
Open

add builtin @__FUNCTION__ macros #6733

srp opened this issue May 3, 2014 · 13 comments
Labels
help wanted Indicates that a maintainer wants help on an issue or pull request

Comments

@srp
Copy link
Contributor

srp commented May 3, 2014

Julia already has @__FILE__, but doesn't appear to have @__LINE__ or @__FUNCTION__, both of which are really helpful when debugging.

@ihnorton
Copy link
Member

Superseded by #8066

@samoconnor
Copy link
Contributor

Hi @ihnorton,
@__FUNCTION__ seems to be still missing.
Is there another way to do get the name of the function that called a macro these days?

I'm trying to do this:

macro require(condition, msg = string(condition))
    esc(:(if ! $condition throw(ArgumentError(string(@__FUNCTION__, " requires ", $msg)) end))
end

function foo(v)
    @require length(v) > 0
    ...
end

foo([])
ArgumentError: foo requires length(v) > 0

https://github.com/JuliaWeb/HTTP.jl/blob/master/src/debug.jl#L38

CC @quinnj @Keno re: JuliaWeb/HTTP.jl#148

@ihnorton
Copy link
Member

ihnorton commented Feb 5, 2018

I see you are using backtrace. Does that not provide what you need? Jameson mentioned the idea of __function__ over in 22064, but I don't think it is implemented yet.

@samoconnor
Copy link
Contributor

With the backtrace implementation we were seeing attempt to access 1-element Array{StackFrame,1} at index [2] Stacktrace: in FemtoCleaner.jl logs. See: JuliaWeb/HTTP.jl#148 (comment)

Using backtrace feels like a bit of a hack, and seems likely to be fragile in the presence of compiler optimisations (we were using @noinline but still saw the bounds error occasionally).

It would be great to have something like @FUNCITON and or @METHOD.

@ViralBShah
Copy link
Member

Reopening at the request of @samoconnor.

@ViralBShah ViralBShah reopened this Apr 18, 2018
@ViralBShah ViralBShah changed the title add builtin @__LINE__ and @__FUNCTION__ macros add builtin @__FUNCTION__ macros Apr 18, 2018
@c42f
Copy link
Member

c42f commented Nov 29, 2019

Does anyone have a particular implementation in mind here? One possibility would be to add a func field to the special __source__ variable containing the statically known name of the caller function as a symbol (or nothing for top level). Though this would mean tracking the current function name during macro expansion (arguably inappropriate to do prior to syntax lowering) and couldn't easily work for closures.

Another idea could be to have a special Expr(:current_function) which expands to the current function name sometime during lowering, maybe after closure conversion. That would mean macros would not be able to see the name of the calling function as it would be filled in later. There might also be some fiddly issues related to implicitly generated closures.

@Go4itOttersoft
Copy link

Still not implemented in Julia 1.5.4 or am I missing something?

@simeonschaub
Copy link
Member

It is actually possible to implement this without special lowering, but it is relying on internals of course:

julia> macro __FUNCTION__()
           return :($(esc(Expr(:isdefined, :var"#self#"))) ? $(esc(:var"#self#")) : nothing)
       end
@__FUNCTION__ (macro with 1 method)

julia> @__FUNCTION__() === nothing
true

julia> f() = @__FUNCTION__
f (generic function with 1 method)

julia> f()
f (generic function with 1 method)

You can even get just the name:

julia> f() = nameof(@__FUNCTION__)
f (generic function with 1 method)

julia> f()
:f

The question is whether this is something we want to officially expose.

@c42f
Copy link
Member

c42f commented Sep 14, 2021

It is actually possible to implement this without special lowering

I love this hack :-) I wonder, is it possible to make it work when @__FUNCTION__ is used within another macro? It seems we run into escaping problems in that case:

julia> module X
       macro __FUNCTION__()
           return :($(esc(Expr(:isdefined, :var"#self#"))) ? $(esc(:var"#self#")) : nothing)
       end
       end

julia> macro A()
           :(X.@__FUNCTION__)
       end

this works:

julia> @macroexpand f() = X.@__FUNCTION__
:(f() = begin
          #= REPL[18]:1 =#
          if $(Expr(:isdefined, Symbol("#self#")))
              var"#self#"
          else
              Main.X.nothing
          end
      end)

whereas this doesn't because #self# is resolved as a global in Main

julia> @macroexpand f() = @A
:(f() = begin
          #= REPL[17]:1 =#
          if $(Expr(:isdefined, :(Main.:(var"#self#"))))
              Main.:(var"#self#")
          else
              Main.X.nothing
          end
      end)

@simeonschaub
Copy link
Member

I think that's a more general issue with macro hygiene and composability of macros. The easiest way would probably be to do the macro expansion manually inside of the macro @A instead of producing :macrocall expressions:

julia> macro A()
           X.var"@__FUNCTION__"(__source__, __module__)
       end
@A (macro with 1 method)

julia> @macroexpand f() = @A
:(f() = begin
          #= REPL[7]:1 =#
          if $(Expr(:isdefined, Symbol("#self#")))
              var"#self#"
          else
              Main.nothing
          end
      end)

Playing around with this, I started to wonder, whether this kind of nested escaping of only a macro's body could itself be inplemented as a macro and it turns out it can:

julia> macro esc(x::Expr)
           @assert x.head === :macrocall
           return esc(Core.eval(__module__, x.args[1])(x.args[2], __module__, x.args[3:end]...))
       end
@esc (macro with 1 method)

julia> macro A()
           :(@esc X.@__FUNCTION__)
       end
@A (macro with 1 method)

julia> @macroexpand f() = @A
:(f() = begin
          #= REPL[25]:1 =#
          if $(Expr(:isdefined, Symbol("#self#")))
              var"#self#"
          else
              Main.nothing
          end
      end)

It can even be used in front of the macrocall to @A itself:

julia> macro A()
           :(X.@__FUNCTION__)
       end
@A (macro with 1 method)

julia> @macroexpand f() = @esc @A
:(f() = begin
          #= REPL[22]:1 =#
          if $(Expr(:isdefined, Symbol("#self#")))
              var"#self#"
          else
              Main.X.nothing
          end
      end)

Not sure if this is actually that useful, but I find it quite fun to play around with. It probably also doesn't really address the issue you were pointing out, since the user still needs to be the one taking care of escaping for such a definition of the @__FUNCTION__ macro.

@vtjnash
Copy link
Member

vtjnash commented Sep 14, 2021

yes, it is a general problem that esc() defines the scope of both the macro lookup and the macro result, so it is hard to properly express if you want the macro itself to be in the current scope, but the result to be in the macro scope. I believe this is currently the most correct solution possible, though it requires writing the current module name explicitly:

julia> macro A()
       esc(:($Main.@inline f()))
       end
@A (macro with 1 method)

julia> @macroexpand1 @A
:((Main).@inline f())

in the above example, that would look like this:

julia> module X
       macro __FUNCTION__()
           return :($(esc(Expr(:isdefined, :var"#self#"))) ? $(esc(:var"#self#")) : nothing)
       end
       end
Main.X

julia> macro A()
           :(X.@__FUNCTION__)
       end
@A (macro with 1 method)

julia> macro A()
           esc(:($X.@__FUNCTION__))
       end
@A (macro with 1 method)

julia> f() = @A
f (generic function with 1 method)

julia> f()
f (generic function with 1 method)

I think the main question would be of what precisely it would return (a Method, Symbol, or Function object). There's already precedent for this sort of reflection feature, so it just remains for someone to add this:

julia> f(x, ::Any) = Base.@locals
f (generic function with 3 methods)

julia> f(1, 2)
Dict{Symbol, Any} with 1 entry:
  :x => 1

@c42f
Copy link
Member

c42f commented Sep 15, 2021

I think the main question would be of what precisely it would return (a Method, Symbol, or Function object).

Yes.

I've only ever wanted the Symbol in practice, but nameof gives a way to access that easily for Function. Returning a Method seems intriguing but I don't have any use case in mind for it (would that be better named @__METHOD__ anyway?)

Also, what would @__FUNCTION__ return for function-like objects? If we return the function instance for normal functions, closure object for closures, returning the object instance seems symmetric. It's a bit less useful though if you just want the "name of the current function for debugging".

@rofinn
Copy link
Contributor

rofinn commented Jun 3, 2022

FWIW, my vote would be for it to just return a symbol that could be used for debugging or in another macro to build up a new expression. I do think a separate @__METHOD__ should probably also exist at some point, but that seems more complicated to implement efficiently and I'm less clear on when that would be useful for folks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Indicates that a maintainer wants help on an issue or pull request
Projects
None yet
Development

No branches or pull requests

9 participants