Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
44 changes: 44 additions & 0 deletions base/runtime_internals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,50 @@ 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__

Get the innermost enclosing function object.

!!! note
Some macros, including [`@spawn`](@ref Threads.@spawn), [`@async`](@ref), etc.,
wrap their code in closures. When `@__FUNCTION__` is used within such code,
it will refer to the closure created by the macro rather than the enclosing
user-defined function. This follows the same scoping behavior as `return`:
just as `return` exits the closure and not the outer function, `@__FUNCTION__`
refers to the closure and not the outer function.

# Examples

`@__FUNCTION__` enables recursive anonymous functions:

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

julia> factorial(5)
120
```

`@__FUNCTION__` can be combined with `nameof` to identify a function's
name from within its body:

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

julia> bar()
:bar
```
"""
macro __FUNCTION__()
esc(quote
false && (var"#self#" = var"#self#") # declare self is a local variable to avoid polluting or accessing globals
$(Expr(:isdefined, :var"#self#")) || $Base._function_macro_error()
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
34 changes: 34 additions & 0 deletions doc/src/manual/performance-tips.md
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,40 @@ In the mean time, some user-contributed packages like
[FastClosures](https://github.com/c42f/FastClosures.jl) automate the
insertion of `let` statements as in `abmult3`.

#### Use `@__FUNCTION__` for recursive closures

For recursive closures specifically, the [`@__FUNCTION__`](@ref) macro can avoid both type instability and boxing.

First, let's see the unoptimized version:

```julia
function make_fib_unoptimized()
fib(n) = n <= 1 ? 1 : fib(n - 1) + fib(n - 2) # fib is boxed
return fib
end
```

The `fib` function is boxed, meaning the return type is inferred as `Any`:

```julia
@code_warntype make_fib_unoptimized()
```

Now, to eliminate this type instability, we can instead use `@__FUNCTION__` to refer to the concrete function object:

```julia
function make_fib_optimized()
fib(n) = n <= 1 ? 1 : (@__FUNCTION__)(n - 1) + (@__FUNCTION__)(n - 2)
return fib
end
```

This gives us a concrete return type:

```julia
@code_warntype make_fib_optimized()
```


### [Types with values-as-parameters](@id man-performance-value-type)

Expand Down
94 changes: 94 additions & 0 deletions test/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,100 @@ 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 "Compatibility with kwargs" begin
foo(; n) = n <= 1 ? 1 : n * (@__FUNCTION__)(; n = n - 1)
@test foo(n = 5) == 120
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_broken 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