Skip to content

Commit 9477a24

Browse files
authored
add @__FUNCTION__ macro (#841)
1 parent 743ee8c commit 9477a24

File tree

4 files changed

+239
-2
lines changed

4 files changed

+239
-2
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "Compat"
22
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
3-
version = "4.17.0"
3+
version = "4.18.0"
44

55
[deps]
66
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ changes in `julia`.
7272

7373
## Supported features
7474

75+
* `@__FUNCTION__` is a macro that returns the innermost enclosing function ([#58940]) (since Compat 4.18.0).
76+
* *Note: the Compat version of this macro does not work in callable structs.*
77+
7578
* `filter` can now act on a `NamedTuple` [#50795]. (since Compat 4.17.0)
7679

7780
* `insertdims(D; dims)` is the opposite of `dropdims` ([#45793]) (since Compat 4.16.0)
@@ -201,3 +204,4 @@ Note that you should specify the correct minimum version for `Compat` in the
201204
[#50105]: https://github.com/JuliaLang/julia/issues/50105
202205
[#50795]: https://github.com/JuliaLang/julia/issues/50795
203206
[#54653]: https://github.com/JuliaLang/julia/issues/54653
207+
[#58940]: https://github.com/JuliaLang/julia/issues/58940

src/Compat.jl

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,6 +1229,60 @@ else
12291229
using Base: Fix, Fix1, Fix2
12301230
end
12311231

1232+
# https://github.com/JuliaLang/julia/pull/58940
1233+
@static if !isdefined(Base, Symbol("@__FUNCTION__"))
1234+
_function_macro_error() = throw(ArgumentError("`Compat.@__FUNCTION__` is not available in this context"))
1235+
1236+
macro __FUNCTION__()
1237+
esc(quote
1238+
$(Expr(:isdefined, :var"#self#")) || $(_function_macro_error)()
1239+
var"#self#"
1240+
end)
1241+
end
1242+
1243+
@doc """
1244+
@__FUNCTION__
1245+
1246+
Get the innermost enclosing function object.
1247+
1248+
!!! warning
1249+
The Compat.jl version of `@__FUNCTION__` differs from the Base version in
1250+
VERSION < v"1.13.0-DEV.880" in two contexts:
1251+
(1) it _will_ throw an error when used in certain contexts (such as callable structs), and
1252+
(2) it will _not_ throw an error when used inside a comprehension or generator.
1253+
1254+
!!! note
1255+
`@__FUNCTION__` has the same scoping behavior as `return`: when used
1256+
inside a closure, it refers to the closure and not the outer function.
1257+
Some macros, including [`@spawn`](@ref Threads.@spawn), [`@async`](@ref), etc.,
1258+
wrap their input in closures. When `@__FUNCTION__` is used within such code,
1259+
it will refer to the closure created by the macro rather than the enclosing function.
1260+
1261+
# Examples
1262+
1263+
`@__FUNCTION__` enables recursive anonymous functions:
1264+
1265+
```jldoctest
1266+
julia> factorial = (n -> n <= 1 ? 1 : n * (@__FUNCTION__)(n - 1));
1267+
1268+
julia> factorial(5)
1269+
120
1270+
```
1271+
1272+
`@__FUNCTION__` can be combined with `nameof` to identify a function's
1273+
name from within its body:
1274+
1275+
```jldoctest
1276+
julia> bar() = nameof(@__FUNCTION__);
1277+
1278+
julia> bar()
1279+
:bar
1280+
```
1281+
""" :(@__FUNCTION__)
1282+
1283+
export @__FUNCTION__
1284+
end
1285+
12321286
include("deprecated.jl")
12331287

12341288
end # module Compat

test/runtests.jl

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1079,4 +1079,183 @@ end
10791079
longnt = NamedTuple{ntuple(i -> Symbol(:a, i), 20)}(ntuple(identity, 20))
10801080
@test filter(iseven, longnt) === NamedTuple{ntuple(i -> Symbol(:a, 2i), 10)}(ntuple(i -> 2i, 10))
10811081
@test filter(x -> x<2, (longnt..., z=1.5)) === (a1=1, z=1.5)
1082-
end
1082+
end
1083+
1084+
# https://github.com/JuliaLang/julia/pull/58940
1085+
@testset "@__FUNCTION__" begin
1086+
@testset "Basic usage" begin
1087+
# @__FUNCTION__ in regular functions
1088+
test_function_basic() = @__FUNCTION__
1089+
@test test_function_basic() === test_function_basic
1090+
end
1091+
1092+
@testset "Recursion" begin
1093+
# Factorial with @__FUNCTION__
1094+
factorial_function(n) = n <= 1 ? 1 : n * (@__FUNCTION__)(n - 1)
1095+
@test factorial_function(5) == 120
1096+
1097+
# Fibonacci with @__FUNCTION__
1098+
struct RecursiveCallableStruct; end
1099+
@eval (::RecursiveCallableStruct)(n) = n <= 1 ? n : (@__FUNCTION__)(n-1) + (@__FUNCTION__)(n-2)
1100+
@test RecursiveCallableStruct()(10) === 55
1101+
1102+
# Anonymous function recursion
1103+
@test (n -> n <= 1 ? 1 : n * (@__FUNCTION__)(n - 1))(5) == 120
1104+
end
1105+
1106+
@testset "Closures and nested functions" begin
1107+
# Prevents boxed closures
1108+
function make_closure()
1109+
fib(n) = n <= 1 ? 1 : (@__FUNCTION__)(n - 1) + (@__FUNCTION__)(n - 2)
1110+
return fib
1111+
end
1112+
Test.@inferred make_closure()
1113+
closure = make_closure()
1114+
@test closure(5) == 8
1115+
Test.@inferred closure(5)
1116+
1117+
# Complex closure of closures
1118+
function f1()
1119+
function f2()
1120+
function f3()
1121+
return @__FUNCTION__
1122+
end
1123+
return (@__FUNCTION__), f3()
1124+
end
1125+
return (@__FUNCTION__), f2()...
1126+
end
1127+
Test.@inferred f1()
1128+
@test f1()[1] === f1
1129+
@test f1()[2] !== f1
1130+
@test f1()[3] !== f1
1131+
@test f1()[3]() === f1()[3]
1132+
@test f1()[2]()[2]() === f1()[3]
1133+
end
1134+
1135+
@testset "Do blocks" begin
1136+
function test_do_block()
1137+
result = map([1, 2, 3]) do x
1138+
return (@__FUNCTION__, x)
1139+
end
1140+
# All should refer to the same do-block function
1141+
@test all(r -> r[1] === result[1][1], result)
1142+
# Values should be different
1143+
@test [r[2] for r in result] == [1, 2, 3]
1144+
# It should be different than `test_do_block`
1145+
@test result[1][1] !== test_do_block
1146+
end
1147+
test_do_block()
1148+
end
1149+
1150+
@testset "Keyword arguments" begin
1151+
# @__FUNCTION__ with kwargs
1152+
foo(; n) = n <= 1 ? 1 : n * (@__FUNCTION__)(; n = n - 1)
1153+
@test foo(n = 5) == 120
1154+
end
1155+
1156+
@testset "Callable structs" begin
1157+
# @__FUNCTION__ in callable structs
1158+
@gensym A
1159+
@eval module $A
1160+
import ..@__FUNCTION__
1161+
1162+
struct CallableStruct{T}; val::T; end
1163+
(c::CallableStruct)() = @__FUNCTION__
1164+
end
1165+
@eval using .$A: CallableStruct
1166+
c = CallableStruct(5)
1167+
if isdefined(Base, Symbol("@__FUNCTION__"))
1168+
@test c() === c
1169+
else
1170+
@test_broken c() === c
1171+
end
1172+
1173+
# In closures, var"#self#" should refer to the enclosing function,
1174+
# NOT the enclosing struct instance
1175+
struct CallableStruct2; end
1176+
@eval function (obj::CallableStruct2)()
1177+
function inner_func()
1178+
@__FUNCTION__
1179+
end
1180+
inner_func
1181+
end
1182+
1183+
let cs = CallableStruct2()
1184+
@test cs()() === cs()
1185+
@test cs()() !== cs
1186+
end
1187+
1188+
# Accessing values via self-reference
1189+
struct CallableStruct3
1190+
value::Int
1191+
end
1192+
@eval (obj::CallableStruct3)() = @__FUNCTION__
1193+
@eval (obj::CallableStruct3)(x) = (@__FUNCTION__).value + x
1194+
1195+
let cs = CallableStruct3(42)
1196+
if isdefined(Base, Symbol("@__FUNCTION__"))
1197+
@test cs() === cs
1198+
@test cs(10) === 52
1199+
else
1200+
@test_broken cs() === cs
1201+
@test_broken cs(10) === 52
1202+
end
1203+
end
1204+
1205+
# Callable struct with args and kwargs
1206+
struct CallableStruct4
1207+
end
1208+
@eval function (obj::CallableStruct4)(x, args...; y=2, kws...)
1209+
return (; func=(@__FUNCTION__), x, args, y, kws)
1210+
end
1211+
c = CallableStruct4()
1212+
if isdefined(Base, Symbol("@__FUNCTION__"))
1213+
@test c(1).func === c
1214+
@test c(2, 3).args == (3,)
1215+
@test c(2; y=4).y == 4
1216+
@test c(2; y=4, a=5, b=6, c=7).kws[:c] == 7
1217+
else
1218+
@test_broken c(1).func === c
1219+
@test_broken c(2, 3).args == (3,)
1220+
@test_broken c(2; y=4).y == 4
1221+
@test_broken c(2; y=4, a=5, b=6, c=7).kws[:c] == 7
1222+
end
1223+
end
1224+
1225+
@testset "Special cases" begin
1226+
# Generated functions
1227+
let @generated foo2() = :(@__FUNCTION__)
1228+
@test foo2() === foo2
1229+
end
1230+
1231+
# Should not access arg-map for local variables
1232+
@gensym f
1233+
@eval begin
1234+
function $f end
1235+
function ($f::typeof($f))()
1236+
$f = 1
1237+
@__FUNCTION__
1238+
end
1239+
end
1240+
if isdefined(Base, Symbol("@__FUNCTION__"))
1241+
@test @eval($f() === $f)
1242+
else
1243+
@test_broken @eval($f() === $f)
1244+
end
1245+
end
1246+
1247+
if isdefined(Base, Symbol("@__FUNCTION__"))
1248+
@testset "Error upon misuse" begin
1249+
@gensym B
1250+
@test_throws(
1251+
"\"@__FUNCTION__\" can only be used inside a function",
1252+
@eval(module $B; @__FUNCTION__; end)
1253+
)
1254+
1255+
@test_throws(
1256+
"\"@__FUNCTION__\" not allowed inside comprehension or generator",
1257+
@eval([(@__FUNCTION__) for _ in 1:10])
1258+
)
1259+
end
1260+
end
1261+
end

0 commit comments

Comments
 (0)