Skip to content

Define @__FUNCTION__ as an alias to var"#self#" #58909

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

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ New language features

- New `Base.@acquire` macro for a non-closure version of `Base.acquire(f, s::Base.Semaphore)`, like `@lock`. ([#56845])
- New `nth` function to access the `n`-th element of a generic iterable. ([#56580])
- New `@__FUNCTION__` macro that returns a reference to the innermost enclosing function. ([#58909])
- The character U+1F8B2 🢲 (RIGHTWARDS ARROW WITH LOWER HOOK), newly added by Unicode 16,
is now a valid operator with arrow precedence, accessible as `\hookunderrightarrow` at the REPL.
([JuliaLang/JuliaSyntax.jl#525], [#57143])
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,7 @@ export
@__DIR__,
@__LINE__,
@__MODULE__,
@__FUNCTION__,
@int128_str,
@uint128_str,
@big_str,
Expand Down
64 changes: 64 additions & 0 deletions base/runtime_internals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,70 @@ false
"""
ispublic(m::Module, s::Symbol) = ccall(:jl_module_public_p, Cint, (Any, Any), m, s) != 0

_function_macro_error() = (@noinline; error("@__FUNCTION__ can only be used within a function"))

"""
@__FUNCTION__ -> Function

Get the innermost enclosing function object.

!!! note
In functions like `f() = [(@__FUNCTION__) for _ in 1:10]`, this would
refer to the generator function in the comprehension, NOT the enclosing
function `f`. Similarly, in a closure function, this will refer to the
closure function object rather than the enclosing function. Note that
macros like [`@spawn`](@ref Threads.@spawn), [`@async`](@ref), etc., also
create closures.

!!! note
This does not work in the context of callable structs as there is no
function to refer to, and will result in an `UndefVarError`.

# Examples

`@__FUNCTION__` is useful for closures that need to refer to themselves,
as otherwise the function object would be captured as a variable and boxed.

```jldoctest
julia> function make_fib()
fib(n) = n <= 1 ? 1 : (@__FUNCTION__)(n - 1) + (@__FUNCTION__)(n - 2)
return fib
end
make_fib (generic function with 1 method)

julia> make_fib()(7)
21
```

If we had instead used `fib(n - 1) + fib(n - 2)` directly, `fib` would be boxed,
leading to type instabilities.

Note that `@__FUNCTION__` is also available for anonymous functions:

```jldoctest
julia> factorial = n -> n <= 1 ? 1 : n * (@__FUNCTION__)(n - 1);

julia> factorial(5)
120
```

`@__FUNCTION__` can also be combined with `nameof` to get the symbol for an
enclosing function:

```jldoctest
julia> bar() = nameof(@__FUNCTION__);

julia> bar()
:bar
```
"""
macro __FUNCTION__()
quote
$(esc(Expr(:isdefined, :var"#self#"))) || $(esc(_function_macro_error))()
$(esc(:var"#self#"))
end
end

# TODO: this is vaguely broken because it only works for explicit calls to
# `Base.deprecate`, not the @deprecated macro:
isdeprecated(m::Module, s::Symbol) = ccall(:jl_is_binding_deprecated, Cint, (Any, Any), m, s) != 0
Expand Down
1 change: 1 addition & 0 deletions doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ Base.moduleroot
__module__
__source__
Base.@__MODULE__
Base.@__FUNCTION__
Base.@__FILE__
Base.@__DIR__
Base.@__LINE__
Expand Down
89 changes: 89 additions & 0 deletions test/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,95 @@ let exename = `$(Base.julia_cmd()) --compiled-modules=yes --startup-file=no --co
@test !endswith(s_dir, Base.Filesystem.path_separator)
end

@testset "Tests for @__FUNCTION__" begin
let
@testset "Basic usage" begin
test_function_basic() = @__FUNCTION__
@test test_function_basic() === test_function_basic
end

@testset "Factorial function" begin
factorial_function(n) = n <= 1 ? 1 : n * (@__FUNCTION__)(n - 1)
@test factorial_function(5) == 120
end

@testset "Prevents boxed closures" begin
function make_closure()
fib(n) = n <= 1 ? 1 : (@__FUNCTION__)(n - 1) + (@__FUNCTION__)(n - 2)
return fib
end
Test.@inferred make_closure()
closure = make_closure()
@test closure(5) == 8
Test.@inferred closure(5)
end

@testset "Will return innermost, even if comprehension" begin
f() = [(@__FUNCTION__) for _ in 1:10]

funcs = f()
@test first(funcs) !== f
@test all(fi -> fi === funcs[1], funcs[2:end])
end

@testset "Complex closure of closures" begin
function f1()
function f2()
function f3()
return @__FUNCTION__
end
return (@__FUNCTION__), f3()
end
return (@__FUNCTION__), f2()...
end
Test.@inferred f1()
@test f1()[1] === f1
@test f1()[2] !== f1
@test f1()[3] !== f1
@test f1()[3]() === f1()[3]
@test f1()[2]()[2]() === f1()[3]
end

@testset "Anonymous function" begin
@test (n -> n <= 1 ? 1 : n * (@__FUNCTION__)(n - 1))(5) == 120
end

@testset "Do block" begin
function test_do_block()
result = map([1, 2, 3]) do x
return (@__FUNCTION__, x)
end
# All should refer to the same do-block function
@test all(r -> r[1] === result[1][1], result)
# Values should be different
@test [r[2] for r in result] == [1, 2, 3]
# It should be different than `test_do_block`
@test result[1][1] !== test_do_block
end
test_do_block()
end

@testset "Error upon misuse" begin
@gensym A
@test_throws(
"@__FUNCTION__ can only be used within a function",
@eval(module $A; @__FUNCTION__; end)
)
end

@testset "Callable structs throw error" begin
@gensym A
@eval module $A
struct CallableStruct{T}; val::T; end
(c::CallableStruct)() = @__FUNCTION__
end
@eval using .$A: CallableStruct
c = CallableStruct(5)
@test_throws "@__FUNCTION__ can only be used within a function" c()
end
end
end

@test Base.in_sysimage(Base.PkgId(Base.UUID("8f399da3-3557-5675-b5ff-fb832c97cbdb"), "Libdl"))
@test Base.in_sysimage(Base.PkgId(Base.UUID("3a7fdc7e-7467-41b4-9f64-ea033d046d5b"), "NotAPackage")) == false

Expand Down
Loading