Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
8a4e772
feat: enable `var"#self#"` for callable structs
MilesCranmer Jul 5, 2025
e72edfc
add tests for `var"#self#"` in callable structs
MilesCranmer Jul 5, 2025
048f6fa
Merge branch 'master' into callable-struct-self
MilesCranmer Jul 6, 2025
41c31b5
Update src/julia-syntax.scm
MilesCranmer Jul 8, 2025
e1f2cf3
remove redundant `and`
MilesCranmer Jul 8, 2025
db24816
create `Expr(:thisfunction) -> var"#self#"`
MilesCranmer Jul 8, 2025
23e7678
change tests to use `Expr(:thisfunction)`
MilesCranmer Jul 8, 2025
b0da142
fix inclusion
MilesCranmer Jul 8, 2025
50afcde
ensure we also check for (thisfunction) when referencing
MilesCranmer Jul 8, 2025
bd7f004
simplify Expr(:thisfunction) processing
MilesCranmer Jul 10, 2025
f116edd
example approach to kw compat for :thisfunction
MilesCranmer Jul 10, 2025
e503d3a
fix callable struct error
MilesCranmer Jul 11, 2025
d6adc73
handle ctor-self
MilesCranmer Jul 11, 2025
8495ca9
fix edge cases of :thisfunction parsing
MilesCranmer Jul 11, 2025
dbb1f7b
Update src/julia-syntax.scm
MilesCranmer Jul 11, 2025
d01c30b
remove incorrect effect for thisfunction
MilesCranmer Jul 11, 2025
da85566
use expr-contains-p
MilesCranmer Jul 11, 2025
6f355ad
simplify parts of thisfunction expansion
MilesCranmer Jul 11, 2025
5509f6c
simplify final-name and use it
MilesCranmer Jul 11, 2025
8ab267a
handle non-symbol argname
MilesCranmer Jul 11, 2025
e753709
unneccessary comment
MilesCranmer Jul 11, 2025
68c1987
fix slot idx error with explicit variable name
MilesCranmer Jul 11, 2025
b1f858f
make sure we gen the #self#
MilesCranmer Jul 11, 2025
56632e5
put back argmap as required for kwcall
MilesCranmer Jul 11, 2025
8f7cca2
improve tests of :thisfunction
MilesCranmer Jul 11, 2025
787320b
Update test/syntax.jl
MilesCranmer Jul 11, 2025
5086df0
fix: disallow `thisfunction` inside comprehension or generator
MilesCranmer Jul 12, 2025
7d883e1
test: add test with generated function example
MilesCranmer Jul 12, 2025
aec2984
add `@__FUNCTION__` macro from https://github.com/JuliaLang/julia/pul…
MilesCranmer Jul 17, 2025
a3965bc
update error test
MilesCranmer Jul 17, 2025
8433123
move tests to syntax.jl
MilesCranmer Jul 17, 2025
53f3515
thisfunction error should refer to macro
MilesCranmer Jul 17, 2025
fde0e61
add test against comprehension
MilesCranmer Jul 17, 2025
480f44d
kw test broken for some reason
MilesCranmer Jul 17, 2025
d9b6164
Update src/julia-syntax.scm
MilesCranmer Jul 17, 2025
452aac0
avoid globalref in `thisfunction` reference as not needed
MilesCranmer Jul 17, 2025
dbfeae2
fix: remove added line
MilesCranmer Jul 17, 2025
1f89559
add jeff's edge cases to tests
MilesCranmer Jul 17, 2025
28f74f0
fixes
JeffBezanson Jul 17, 2025
0577319
tests no longer broken
MilesCranmer Jul 17, 2025
8636f11
fix eval block
MilesCranmer Jul 17, 2025
337a593
prevent name collisions in test
MilesCranmer Jul 17, 2025
02a5759
Merge branch 'master' into thisfunction-expr
MilesCranmer Jul 18, 2025
49bf1fe
update Meta.lower test for new error message
MilesCranmer Jul 18, 2025
23caad0
tighten up docstring
MilesCranmer Jul 18, 2025
9f3bdb5
refer to macro in misuse error message
MilesCranmer Jul 18, 2025
f49718e
tweak NEWS
MilesCranmer Jul 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 to refer 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])
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,7 @@ export
@__DIR__,
@__LINE__,
@__MODULE__,
@__FUNCTION__,
@int128_str,
@uint128_str,
@big_str,
Expand Down
37 changes: 37 additions & 0 deletions base/runtime_internals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,43 @@ false
"""
ispublic(m::Module, s::Symbol) = ccall(:jl_module_public_p, Cint, (Any, Any), m, s) != 0

"""
@__FUNCTION__

Get the innermost enclosing function object.

!!! note
`@__FUNCTION__` has the same scoping behavior as `return`: when used
inside a closure, it refers to the closure and not the outer function.
Some macros, including [`@spawn`](@ref Threads.@spawn), [`@async`](@ref), etc.,
wrap their input in closures. When `@__FUNCTION__` is used within such code,
it will refer to the closure created by the macro rather than the enclosing 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__()
Expr(:thisfunction)
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
Expand Down
1 change: 1 addition & 0 deletions doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ Base.moduleroot
__module__
__source__
Base.@__MODULE__
Base.@__FUNCTION__
Base.@__FILE__
Base.@__DIR__
Base.@__LINE__
Expand Down
34 changes: 34 additions & 0 deletions doc/src/manual/performance-tips.md
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True and useful and ... embarrassing 😅


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)

Expand Down
1 change: 1 addition & 0 deletions src/ast.scm
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@
(define (make-assignment l r) `(= ,l ,r))
(define (assignment? e) (and (pair? e) (eq? (car e) '=)))
(define (return? e) (and (pair? e) (eq? (car e) 'return)))
(define (thisfunction? e) (and (pair? e) (eq? (car e) 'thisfunction)))

(define (tuple-call? e)
(and (length> e 1)
Expand Down
43 changes: 39 additions & 4 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,9 @@
(insert-after-meta `(block
,@stmts)
(cons `(meta nkw ,(+ (length vars) (length restkw)))
annotations))
(if (has-thisfunction? `(block ,@stmts))
(cons `(meta thisfunction-original ,(arg-name (car not-optional))) annotations)
annotations)))
rett)

;; call with no keyword args
Expand Down Expand Up @@ -2911,6 +2913,7 @@
'generator
(lambda (e)
(check-no-return e)
(check-no-thisfunction e)
(expand-generator e #f '()))

'flatten
Expand Down Expand Up @@ -2995,6 +2998,13 @@
(if (has-return? e)
(error "\"return\" not allowed inside comprehension or generator")))

(define (has-thisfunction? e)
(expr-contains-p thisfunction? e (lambda (x) (not (function-def? x)))))

(define (check-no-thisfunction e)
(if (has-thisfunction? e)
(error "\"@__FUNCTION__\" not allowed inside comprehension or generator")))

(define (has-break-or-continue? e)
(expr-contains-p (lambda (x) (and (pair? x) (memq (car x) '(break continue))))
e
Expand All @@ -3003,6 +3013,7 @@

(define (lower-comprehension ty expr itrs)
(check-no-return expr)
(check-no-thisfunction expr)
(if (has-break-or-continue? expr)
(error "break or continue outside loop"))
(let ((result (make-ssavalue))
Expand Down Expand Up @@ -3434,7 +3445,7 @@
vi)
tab))

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

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

((error)
(error (cadr e)))

;; thisfunction replaced with first argument name
((thisfunction)
(let ((first-arg (and (pair? (lam:args lam)) (car (lam:args lam)))))
(if first-arg
(let* ((arg-name (arg-name first-arg))
;; Check for thisfunction-original metadata in keyword wrapper functions
(original-name (let ((body (lam:body lam)))
(and (pair? body) (pair? (cdr body))
(let loop ((stmts (cdr body)))
(if (pair? stmts)
(let ((stmt (car stmts)))
(if (and (pair? stmt) (eq? (car stmt) 'meta)
(pair? (cdr stmt)) (eq? (cadr stmt) 'thisfunction-original)
(pair? (cddr stmt)))
(caddr stmt)
(loop (cdr stmts))))
#f)))))
(final-name (or original-name arg-name)))
(cond (tail (emit-return tail final-name))
(value final-name)
(else (emit final-name) #f)))
(error "\"@__FUNCTION__\" can only be used inside a function"))))

(else
(error (string "invalid syntax " (deparse e)))))))
;; introduce new slots for assigned arguments
Expand Down
Loading