Skip to content

Commit 133e189

Browse files
committed
create @__FUNCTION__ alias to var"#self#"
Fixes #58908
1 parent 4846c3d commit 133e189

File tree

3 files changed

+129
-0
lines changed

3 files changed

+129
-0
lines changed

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: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,53 @@ false
173173
"""
174174
ispublic(m::Module, s::Symbol) = ccall(:jl_module_public_p, Cint, (Any, Any), m, s) != 0
175175

176+
"""
177+
@__FUNCTION__ -> Function
178+
179+
Get the innermost enclosing function object.
180+
181+
!!! note
182+
In functions like `f() = [@__FUNCTION__ for _ in 1:10]`, this would
183+
refer to the generator function in the comprehension, NOT the enclosing
184+
function `f`. Similarly, in a closure function, this will refer to the
185+
closure function object rather than the enclosing function.
186+
187+
!!! note
188+
This does not work in the context of callable structs as there is no
189+
function to refer to, and will result in an `UndefVarError`.
190+
191+
# Examples
192+
193+
`@__FUNCTION__` is useful for closures that need to refer to themselves,
194+
as otherwise the function object itself would be a variable and would be boxed:
195+
196+
```jldoctest
197+
julia> function make_fib()
198+
fib(n) = n <= 1 ? 1 : (@__FUNCTION__)(n - 1) + (@__FUNCTION__)(n - 2)
199+
return fib
200+
end
201+
make_fib (generic function with 1 method)
202+
203+
julia> make_fib()(7)
204+
21
205+
```
206+
207+
If we had instead written `fib(n) = n <= 1 ? 1 : fib(n - 1) + fib(n - 2)`
208+
for the closure function, `fib` would be treated as a variable, and be boxed.
209+
210+
Note that `@__FUNCTION__` is available for anonymous functions:
211+
212+
```jldoctest
213+
julia> factorial = n -> n <= 1 ? 1 : n * (@__FUNCTION__)(n - 1);
214+
215+
julia> factorial(5)
216+
120
217+
```
218+
"""
219+
macro __FUNCTION__()
220+
return esc(:(var"#self#"))
221+
end
222+
176223
# TODO: this is vaguely broken because it only works for explicit calls to
177224
# `Base.deprecate`, not the @deprecated macro:
178225
isdeprecated(m::Module, s::Symbol) = ccall(:jl_is_binding_deprecated, Cint, (Any, Any), m, s) != 0

test/loading.jl

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,87 @@ 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 "Will return innermost, even if comprehension" begin
90+
f() = [(@__FUNCTION__) for _ in 1:10]
91+
92+
funcs = f()
93+
@test first(funcs) !== f
94+
@test all(fi -> fi === funcs[1], funcs[2:end])
95+
end
96+
97+
@testset "Complex closure of closures" begin
98+
function f1()
99+
function f2()
100+
function f3()
101+
return @__FUNCTION__
102+
end
103+
return (@__FUNCTION__), f3()
104+
end
105+
return (@__FUNCTION__), f2()...
106+
end
107+
Test.@inferred f1()
108+
@test f1()[1] === f1
109+
@test f1()[2] !== f1
110+
@test f1()[3] !== f1
111+
@test f1()[3]() === f1()[3]
112+
@test f1()[2]()[2]() === f1()[3]
113+
end
114+
115+
@testset "Anonymous function" begin
116+
@test (n -> n <= 1 ? 1 : n * (@__FUNCTION__)(n - 1))(5) == 120
117+
end
118+
119+
@testset "Do block" begin
120+
function test_do_block()
121+
result = map([1, 2, 3]) do x
122+
return (@__FUNCTION__, x)
123+
end
124+
# All should refer to the same do-block function
125+
@test all(r -> r[1] === result[1][1], result)
126+
# Values should be different
127+
@test [r[2] for r in result] == [1, 2, 3]
128+
# It should be different than `test_do_block`
129+
@test result[1][1] !== test_do_block
130+
end
131+
test_do_block()
132+
end
133+
134+
@testset "Callable structs throw error" begin
135+
struct CallableStruct{T}
136+
val::T
137+
end
138+
function (c::CallableStruct)()
139+
return @__FUNCTION__
140+
end
141+
c = CallableStruct(5)
142+
@test_throws UndefVarError c()
143+
end
144+
end
145+
end
146+
66147
@test Base.in_sysimage(Base.PkgId(Base.UUID("8f399da3-3557-5675-b5ff-fb832c97cbdb"), "Libdl"))
67148
@test Base.in_sysimage(Base.PkgId(Base.UUID("3a7fdc7e-7467-41b4-9f64-ea033d046d5b"), "NotAPackage")) == false
68149

0 commit comments

Comments
 (0)