diff --git a/NEWS.md b/NEWS.md index 6a3f67ff063bd..c3b77acbad552 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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]) diff --git a/base/exports.jl b/base/exports.jl index 53f6152ea55f2..2c30f095a3998 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1059,6 +1059,7 @@ export @__DIR__, @__LINE__, @__MODULE__, + @__FUNCTION__, @int128_str, @uint128_str, @big_str, diff --git a/base/runtime_internals.jl b/base/runtime_internals.jl index 98dd111ccbf68..a5b27dd0d9f54 100644 --- a/base/runtime_internals.jl +++ b/base/runtime_internals.jl @@ -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 diff --git a/doc/src/base/base.md b/doc/src/base/base.md index f3eb62b3680d5..d30623c195a4a 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -480,6 +480,7 @@ Base.moduleroot __module__ __source__ Base.@__MODULE__ +Base.@__FUNCTION__ Base.@__FILE__ Base.@__DIR__ Base.@__LINE__ diff --git a/doc/src/manual/performance-tips.md b/doc/src/manual/performance-tips.md index b08b71f65db05..8abf11e209728 100644 --- a/doc/src/manual/performance-tips.md +++ b/doc/src/manual/performance-tips.md @@ -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) diff --git a/test/loading.jl b/test/loading.jl index e95138e27f4dc..3b0ebfe7ad6dd 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -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