Skip to content

Commit aec2984

Browse files
committed
add @__FUNCTION__ macro from JuliaLang#58909
1 parent 7d883e1 commit aec2984

File tree

6 files changed

+161
-0
lines changed

6 files changed

+161
-0
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ New language features
66

77
- New `Base.@acquire` macro for a non-closure version of `Base.acquire(f, s::Base.Semaphore)`, like `@lock`. ([#56845])
88
- New `nth` function to access the `n`-th element of a generic iterable. ([#56580])
9+
- New `@__FUNCTION__` macro that returns a reference to the innermost enclosing function. ([#58909])
910
- The character U+1F8B2 🢲 (RIGHTWARDS ARROW WITH LOWER HOOK), newly added by Unicode 16,
1011
is now a valid operator with arrow precedence, accessible as `\hookunderrightarrow` at the REPL.
1112
([JuliaLang/JuliaSyntax.jl#525], [#57143])

base/exports.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,6 +1059,7 @@ export
10591059
@__DIR__,
10601060
@__LINE__,
10611061
@__MODULE__,
1062+
@__FUNCTION__,
10621063
@int128_str,
10631064
@uint128_str,
10641065
@big_str,

base/runtime_internals.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,44 @@ false
173173
"""
174174
ispublic(m::Module, s::Symbol) = ccall(:jl_module_public_p, Cint, (Any, Any), m, s) != 0
175175

176+
"""
177+
@__FUNCTION__
178+
179+
Get the innermost enclosing function object.
180+
181+
!!! note
182+
Some macros, including [`@spawn`](@ref Threads.@spawn), [`@async`](@ref), etc.,
183+
wrap their code in closures. When `@__FUNCTION__` is used within such code,
184+
it will refer to the closure created by the macro rather than the enclosing
185+
user-defined function. This follows the same scoping behavior as `return`:
186+
just as `return` exits the closure and not the outer function, `@__FUNCTION__`
187+
refers to the closure and not the outer function.
188+
189+
# Examples
190+
191+
`@__FUNCTION__` enables recursive anonymous functions:
192+
193+
```jldoctest
194+
julia> factorial = (n -> n <= 1 ? 1 : n * (@__FUNCTION__)(n - 1));
195+
196+
julia> factorial(5)
197+
120
198+
```
199+
200+
`@__FUNCTION__` can be combined with `nameof` to identify a function's
201+
name from within its body:
202+
203+
```jldoctest
204+
julia> bar() = nameof(@__FUNCTION__);
205+
206+
julia> bar()
207+
:bar
208+
```
209+
"""
210+
macro __FUNCTION__()
211+
Expr(:thisfunction)
212+
end
213+
176214
# TODO: this is vaguely broken because it only works for explicit calls to
177215
# `Base.deprecate`, not the @deprecated macro:
178216
isdeprecated(m::Module, s::Symbol) = ccall(:jl_is_binding_deprecated, Cint, (Any, Any), m, s) != 0

doc/src/base/base.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ Base.moduleroot
480480
__module__
481481
__source__
482482
Base.@__MODULE__
483+
Base.@__FUNCTION__
483484
Base.@__FILE__
484485
Base.@__DIR__
485486
Base.@__LINE__

doc/src/manual/performance-tips.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,40 @@ In the mean time, some user-contributed packages like
917917
[FastClosures](https://github.com/c42f/FastClosures.jl) automate the
918918
insertion of `let` statements as in `abmult3`.
919919

920+
#### Use `@__FUNCTION__` for recursive closures
921+
922+
For recursive closures specifically, the [`@__FUNCTION__`](@ref) macro can avoid both type instability and boxing.
923+
924+
First, let's see the unoptimized version:
925+
926+
```julia
927+
function make_fib_unoptimized()
928+
fib(n) = n <= 1 ? 1 : fib(n - 1) + fib(n - 2) # fib is boxed
929+
return fib
930+
end
931+
```
932+
933+
The `fib` function is boxed, meaning the return type is inferred as `Any`:
934+
935+
```julia
936+
@code_warntype make_fib_unoptimized()
937+
```
938+
939+
Now, to eliminate this type instability, we can instead use `@__FUNCTION__` to refer to the concrete function object:
940+
941+
```julia
942+
function make_fib_optimized()
943+
fib(n) = n <= 1 ? 1 : (@__FUNCTION__)(n - 1) + (@__FUNCTION__)(n - 2)
944+
return fib
945+
end
946+
```
947+
948+
This gives us a concrete return type:
949+
950+
```julia
951+
@code_warntype make_fib_optimized()
952+
```
953+
920954

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

test/loading.jl

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,92 @@ let exename = `$(Base.julia_cmd()) --compiled-modules=yes --startup-file=no --co
6363
@test !endswith(s_dir, Base.Filesystem.path_separator)
6464
end
6565

66+
@testset "Tests for @__FUNCTION__" begin
67+
let
68+
@testset "Basic usage" begin
69+
test_function_basic() = @__FUNCTION__
70+
@test test_function_basic() === test_function_basic
71+
end
72+
73+
@testset "Factorial function" begin
74+
factorial_function(n) = n <= 1 ? 1 : n * (@__FUNCTION__)(n - 1)
75+
@test factorial_function(5) == 120
76+
end
77+
78+
@testset "Prevents boxed closures" begin
79+
function make_closure()
80+
fib(n) = n <= 1 ? 1 : (@__FUNCTION__)(n - 1) + (@__FUNCTION__)(n - 2)
81+
return fib
82+
end
83+
Test.@inferred make_closure()
84+
closure = make_closure()
85+
@test closure(5) == 8
86+
Test.@inferred closure(5)
87+
end
88+
89+
@testset "Complex closure of closures" begin
90+
function f1()
91+
function f2()
92+
function f3()
93+
return @__FUNCTION__
94+
end
95+
return (@__FUNCTION__), f3()
96+
end
97+
return (@__FUNCTION__), f2()...
98+
end
99+
Test.@inferred f1()
100+
@test f1()[1] === f1
101+
@test f1()[2] !== f1
102+
@test f1()[3] !== f1
103+
@test f1()[3]() === f1()[3]
104+
@test f1()[2]()[2]() === f1()[3]
105+
end
106+
107+
@testset "Anonymous function" begin
108+
@test (n -> n <= 1 ? 1 : n * (@__FUNCTION__)(n - 1))(5) == 120
109+
end
110+
111+
@testset "Do block" begin
112+
function test_do_block()
113+
result = map([1, 2, 3]) do x
114+
return (@__FUNCTION__, x)
115+
end
116+
# All should refer to the same do-block function
117+
@test all(r -> r[1] === result[1][1], result)
118+
# Values should be different
119+
@test [r[2] for r in result] == [1, 2, 3]
120+
# It should be different than `test_do_block`
121+
@test result[1][1] !== test_do_block
122+
end
123+
test_do_block()
124+
end
125+
126+
@testset "Compatibility with kwargs" begin
127+
foo(; n) = n <= 1 ? 1 : n * (@__FUNCTION__)(; n = n - 1)
128+
@test foo(n = 5) == 120
129+
end
130+
131+
@testset "Error upon misuse" begin
132+
@gensym A
133+
@test_throws(
134+
"@__FUNCTION__ can only be used within a function",
135+
@eval(module $A; @__FUNCTION__; end)
136+
)
137+
end
138+
139+
@testset "Callable structs" begin
140+
@gensym A
141+
@eval module $A
142+
struct CallableStruct{T}; val::T; end
143+
(c::CallableStruct)() = @__FUNCTION__
144+
end
145+
@eval using .$A: CallableStruct
146+
c = CallableStruct(5)
147+
@test c() === c
148+
end
149+
end
150+
end
151+
66152
@test Base.in_sysimage(Base.PkgId(Base.UUID("8f399da3-3557-5675-b5ff-fb832c97cbdb"), "Libdl"))
67153
@test Base.in_sysimage(Base.PkgId(Base.UUID("3a7fdc7e-7467-41b4-9f64-ea033d046d5b"), "NotAPackage")) == false
68154

0 commit comments

Comments
 (0)