Skip to content

Commit bea90a2

Browse files
add @__FUNCTION__ and Expr(:thisfunction) as generic function self-reference (#58940)
This PR adds `@__FUNCTION__` to match the naming conventions of existing reflection macros (`@__MODULE__`, `@__FILE__`, etc.). --------- Co-authored-by: Jeff Bezanson <jeff.bezanson@gmail.com>
1 parent 3b29187 commit bea90a2

File tree

8 files changed

+293
-4
lines changed

8 files changed

+293
-4
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 to refer 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: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,43 @@ 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+
`@__FUNCTION__` has the same scoping behavior as `return`: when used
183+
inside a closure, it refers to the closure and not the outer function.
184+
Some macros, including [`@spawn`](@ref Threads.@spawn), [`@async`](@ref), etc.,
185+
wrap their input in closures. When `@__FUNCTION__` is used within such code,
186+
it will refer to the closure created by the macro rather than the enclosing function.
187+
188+
# Examples
189+
190+
`@__FUNCTION__` enables recursive anonymous functions:
191+
192+
```jldoctest
193+
julia> factorial = (n -> n <= 1 ? 1 : n * (@__FUNCTION__)(n - 1));
194+
195+
julia> factorial(5)
196+
120
197+
```
198+
199+
`@__FUNCTION__` can be combined with `nameof` to identify a function's
200+
name from within its body:
201+
202+
```jldoctest
203+
julia> bar() = nameof(@__FUNCTION__);
204+
205+
julia> bar()
206+
:bar
207+
```
208+
"""
209+
macro __FUNCTION__()
210+
Expr(:thisfunction)
211+
end
212+
176213
# TODO: this is vaguely broken because it only works for explicit calls to
177214
# `Base.deprecate`, not the @deprecated macro:
178215
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
@@ -481,6 +481,7 @@ Base.moduleroot
481481
__module__
482482
__source__
483483
Base.@__MODULE__
484+
Base.@__FUNCTION__
484485
Base.@__FILE__
485486
Base.@__DIR__
486487
Base.@__LINE__

doc/src/manual/performance-tips.md

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

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

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

src/ast.scm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@
466466
(define (make-assignment l r) `(= ,l ,r))
467467
(define (assignment? e) (and (pair? e) (eq? (car e) '=)))
468468
(define (return? e) (and (pair? e) (eq? (car e) 'return)))
469+
(define (thisfunction? e) (and (pair? e) (eq? (car e) 'thisfunction)))
469470

470471
(define (tuple-call? e)
471472
(and (length> e 1)

src/julia-syntax.scm

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,9 @@
549549
(insert-after-meta `(block
550550
,@stmts)
551551
(cons `(meta nkw ,(+ (length vars) (length restkw)))
552-
annotations))
552+
(if (has-thisfunction? `(block ,@stmts))
553+
(cons `(meta thisfunction-original ,(arg-name (car not-optional))) annotations)
554+
annotations)))
553555
rett)
554556

555557
;; call with no keyword args
@@ -2911,6 +2913,7 @@
29112913
'generator
29122914
(lambda (e)
29132915
(check-no-return e)
2916+
(check-no-thisfunction e)
29142917
(expand-generator e #f '()))
29152918

29162919
'flatten
@@ -2995,6 +2998,13 @@
29952998
(if (has-return? e)
29962999
(error "\"return\" not allowed inside comprehension or generator")))
29973000

3001+
(define (has-thisfunction? e)
3002+
(expr-contains-p thisfunction? e (lambda (x) (not (function-def? x)))))
3003+
3004+
(define (check-no-thisfunction e)
3005+
(if (has-thisfunction? e)
3006+
(error "\"@__FUNCTION__\" not allowed inside comprehension or generator")))
3007+
29983008
(define (has-break-or-continue? e)
29993009
(expr-contains-p (lambda (x) (and (pair? x) (memq (car x) '(break continue))))
30003010
e
@@ -3003,6 +3013,7 @@
30033013

30043014
(define (lower-comprehension ty expr itrs)
30053015
(check-no-return expr)
3016+
(check-no-thisfunction expr)
30063017
(if (has-break-or-continue? expr)
30073018
(error "break or continue outside loop"))
30083019
(let ((result (make-ssavalue))
@@ -3434,7 +3445,7 @@
34343445
vi)
34353446
tab))
34363447

3437-
;; env: list of vinfo (includes any closure #self#; should not include globals)
3448+
;; env: list of vinfo (should not include globals)
34383449
;; captvars: list of vinfo
34393450
;; sp: list of symbol
34403451
;; new-sp: list of symbol (static params declared here)
@@ -3855,7 +3866,7 @@ f(x) = yt(x)
38553866
(Set '(quote top core lineinfo line inert local-def unnecessary copyast
38563867
meta inbounds boundscheck loopinfo decl aliasscope popaliasscope
38573868
thunk with-static-parameters toplevel-only
3858-
global globalref global-if-global assign-const-if-global isglobal thismodule
3869+
global globalref global-if-global assign-const-if-global isglobal thismodule thisfunction
38593870
const atomic null true false ssavalue isdefined toplevel module lambda
38603871
error gc_preserve_begin gc_preserve_end export public inline noinline purity)))
38613872

@@ -4093,7 +4104,7 @@ f(x) = yt(x)
40934104
((atom? e) e)
40944105
(else
40954106
(case (car e)
4096-
((quote top core global globalref thismodule lineinfo line break inert module toplevel null true false meta) e)
4107+
((quote top core global globalref thismodule thisfunction lineinfo line break inert module toplevel null true false meta) e)
40974108
((toplevel-only)
40984109
;; hack to avoid generating a (method x) expr for struct types
40994110
(if (eq? (cadr e) 'struct)
@@ -5133,6 +5144,30 @@ f(x) = yt(x)
51335144

51345145
((error)
51355146
(error (cadr e)))
5147+
5148+
;; thisfunction replaced with first argument name
5149+
((thisfunction)
5150+
(let ((first-arg (and (pair? (lam:args lam)) (car (lam:args lam)))))
5151+
(if first-arg
5152+
(let* ((arg-name (arg-name first-arg))
5153+
;; Check for thisfunction-original metadata in keyword wrapper functions
5154+
(original-name (let ((body (lam:body lam)))
5155+
(and (pair? body) (pair? (cdr body))
5156+
(let loop ((stmts (cdr body)))
5157+
(if (pair? stmts)
5158+
(let ((stmt (car stmts)))
5159+
(if (and (pair? stmt) (eq? (car stmt) 'meta)
5160+
(pair? (cdr stmt)) (eq? (cadr stmt) 'thisfunction-original)
5161+
(pair? (cddr stmt)))
5162+
(caddr stmt)
5163+
(loop (cdr stmts))))
5164+
#f)))))
5165+
(final-name (or original-name arg-name)))
5166+
(cond (tail (emit-return tail final-name))
5167+
(value final-name)
5168+
(else (emit final-name) #f)))
5169+
(error "\"@__FUNCTION__\" can only be used inside a function"))))
5170+
51365171
(else
51375172
(error (string "invalid syntax " (deparse e)))))))
51385173
;; introduce new slots for assigned arguments

0 commit comments

Comments
 (0)