Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
2 changes: 1 addition & 1 deletion src/ast.scm
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@

(define (simple-atom? x)
(or (number? x) (string? x) (char? x)
(and (pair? x) (memq (car x) '(ssavalue null true false thismodule)))
(and (pair? x) (memq (car x) '(ssavalue null true false thismodule thisfunction)))
(eq? (typeof x) 'julia_value)))

;; identify some expressions that are safe to repeat
Expand Down
67 changes: 57 additions & 10 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -306,11 +306,17 @@
(map (lambda (x) (replace-vars x renames))
(cdr e))))))

;; Check if an expression contains thisfunction
(define (contains-thisfunction? expr)
(cond ((atom? expr) #f)
((not (pair? expr)) #f)
((eq? (car expr) 'thisfunction) #t)
(else (or (contains-thisfunction? (car expr))
(and (pair? (cdr expr))
(contains-thisfunction? (cdr expr)))))))

(define (make-generator-function name sp-names arg-names body)
(let ((arg-names (append sp-names
(map (lambda (n)
(if (eq? n '|#self#|) (gensy) n))
arg-names))))
(let ((arg-names (append sp-names arg-names)))
(let ((body (insert-after-meta body ;; don't specialize on generator arguments
;; arg-names slots start at 2 (after name)
`((meta nospecialize ,@(map (lambda (idx) `(slot ,(+ idx 2))) (iota (length arg-names))))))))
Expand Down Expand Up @@ -552,7 +558,9 @@
(insert-after-meta `(block
,@stmts)
(cons `(meta nkw ,(+ (length vars) (length restkw)))
annotations))
(if (and (contains-thisfunction? `(block ,@stmts)) name (not (eq? name #f)))
(cons `(meta thisfunction-original ,name) annotations)
annotations)))
rett)

;; call with no keyword args
Expand Down Expand Up @@ -1209,13 +1217,15 @@
(argl (car argl-stmts))
(name (check-dotop (car argl)))
(argname (if (overlay? name) (caddr name) name))
;;
;; fill in first (closure) argument
(self-name (if (nodot-sym-ref? argname) argname (gensy)))
(adj-decl (lambda (n) (if (and (decl? n) (length= n 2))
`(|::| |#self#| ,(cadr n))
`(|::| ,self-name ,(cadr n))
n)))
(farg (if (decl? argname)
(adj-decl argname)
`(|::| |#self#| (call (core Typeof) ,argname))))
`(|::| ,self-name (call (core Typeof) ,argname))))
(body (insert-after-meta body (cdr argl-stmts)))
(argl (cdr argl))
(argl (fix-arglist
Expand Down Expand Up @@ -3437,7 +3447,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 @@ -3858,7 +3868,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 @@ -4096,7 +4106,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 @@ -5136,6 +5146,43 @@ 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 struct constructor by looking for |#ctor-self#| in args
(ctor-self-arg (let ((args (lam:args lam)))
(and (pair? args)
(let loop ((rest args))
(cond ((null? rest) #f)
((eq? (car rest) '|#ctor-self#|) '|#ctor-self#|)
(else (loop (cdr rest))))))))
;; 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 (cond (original-name original-name)
(ctor-self-arg ctor-self-arg)
(else arg-name))))
(let ((e1 (if (and arg-map (symbol? final-name))
(get arg-map final-name final-name)
final-name)))
(cond (tail (emit-return tail e1))
(value e1)
((symbol? e1) (emit e1) #f)
(else (emit e1) #f))))
(error "thisfunction used in context with no arguments"))))

(else
(error (string "invalid syntax " (deparse e)))))))
;; introduce new slots for assigned arguments
Expand Down
37 changes: 37 additions & 0 deletions test/syntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4351,3 +4351,40 @@ let f = NoSpecClosure.K(1)
@test f(2) == 1
@test typeof(f).parameters == Core.svec()
end

# Expr(:thisfunction)
# regular functions can use Expr(:thisfunction) to refer to the function itself
@eval regular_func() = $(Expr(:thisfunction))
@test regular_func() === regular_func

# This also works in callable structs, which refers to the instance
struct CallableStruct
value::Int
end
@eval (obj::CallableStruct)() = $(Expr(:thisfunction))
@eval (obj::CallableStruct)(x) = $(Expr(:thisfunction)).value + x

let cs = CallableStruct(42)
@test cs() === cs
@test cs(10) === 52
end

struct RecursiveCallableStruct; end
@eval (::RecursiveCallableStruct)(n) = n <= 1 ? n : $(Expr(:thisfunction))(n-1) + $(Expr(:thisfunction))(n-2)

@test RecursiveCallableStruct()(10) === 55

# In closures, var"#self#" should refer to the enclosing function,
# NOT the enclosing struct instance
struct CallableStruct2; end
@eval function (obj::CallableStruct2)()
function inner_func()
$(Expr(:thisfunction))
end
inner_func
end

let cs = CallableStruct2()
@test cs()() === cs()
@test cs()() !== cs
end