Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 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
52 changes: 52 additions & 0 deletions base/runtime_internals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,58 @@ 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
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 itself would be a variable and would be 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 written `fib(n) = n <= 1 ? 1 : fib(n - 1) + fib(n - 2)`
for the closure function, `fib` would be treated as a variable, and be boxed.

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

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

julia> factorial(5)
120
```
"""
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