Skip to content
Closed
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