Skip to content

Create Expr(:thisfunction) as generic function self-reference #58940

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 28 commits into
base: master
Choose a base branch
from

Conversation

MilesCranmer
Copy link
Member

This is v2 of #58913 which introduces a directive Expr(:thisfunction) that gets expanded to var"#self#". Expr(:thisfunction) was recommended by @JeffBezanson to complement Expr(:thismodule).

After this PR, the @__FUNCTION__ macro in #58909 could be updated to emit Expr(:thisfunction). Part of the motivation is to ease analysis in lowering tools like JuliaLowering.jl, especially because @__FUNCTION__ would be part of the public API.

@c42f @JeffBezanson let me know if this is close to what you had in mind. I'm not too familiar with this level of Julia so please let me know if I'm doing something wrong.

@JeffBezanson
Copy link
Member

Unless I'm missing something, I think we can remove all the code for #self# here and instead in the compile function replace (thisfunction) with whatever the first argument name is. If the argument is ever re-assigned, we already take care of replacing it with an alias.

@MilesCranmer
Copy link
Member Author

Does core kwcall need special treatment?

@JeffBezanson
Copy link
Member

JeffBezanson commented Jul 10, 2025

Yes, good point, it should return the object the user code called I think. So it should expand to #self# if that argument name exists, otherwise whatever the first argument is. Doesn't work if the user has an argument explicitly called #self# but I think we can punt on that.

@MilesCranmer
Copy link
Member Author

MilesCranmer commented Jul 10, 2025

Ok I think I found a possible way to make it work. I put the function in meta and then read it back during compilation for kwcalls. This seems to let it work for kwcalls. And otherwise we just take the first arg which lets it work for callable structs and functions alike:

julia> struct CallableStruct
           value::Int
       end

julia> @eval (obj::CallableStruct)() = $(Expr(:thisfunction))

julia> CallableStruct(1)()
CallableStruct(1)

julia> @eval f(; n=1) = n <= 1 ? 1 : n * $(Expr(:thisfunction))(; n = n - 1)
f (generic function with 1 method)

julia> f(n=5)
120

julia> @eval foo() = n -> n<=1 ? n : n * $(Expr(:thisfunction))(n-1)  # anonymous closure
foo (generic function with 1 method)

julia> Test.@inferred foo(); Test.@inferred foo()(3)
6

This also seems to fix the kwcall issue @vtjnash pointed out in #58909 (comment)

julia> methods(var"#f#3")
# 1 method for generic function "#f#3" from Main:
 [1] var"#f#3"(n, f::typeof(f))
     @ REPL[8]:1

julia> Base.method_argnames(ans[1])
3-element Vector{Symbol}:
 Symbol("#f#3")
 :n
 :f

^Before, it wasn't able to extract f as a symbol.

Is this the right way to do it?

@MilesCranmer MilesCranmer changed the title Create Expr(:thisfunction) as pre-lowering form of var"#self#" Create Expr(:thisfunction) as generic function self-reference Jul 10, 2025
Co-authored-by: Jeff Bezanson <jeff.bezanson@gmail.com>
(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)))
Copy link
Member

Choose a reason for hiding this comment

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

I think this is handled by the other cases; it always starts as the first argument.

Copy link
Member Author

Choose a reason for hiding this comment

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

This one is written to address some edgecases with parametric type constructors such as

@eval struct Cols{T<:Tuple}
    cols::T
    operator
    Cols(args...; operator=union) = (new{typeof(args)}(args, operator); string($(Expr(:thisfunction))))
end
Cols(1, 2, 3)

Without this logic, this constructor will returns "_". With it, it correctly returns "Cols"

Copy link
Member Author

Choose a reason for hiding this comment

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

Correction: that was actually my REPL acting up. Actually, without this block logic, it gives:

ERROR: syntax: malformed expression

@JeffBezanson
Copy link
Member

I do love how this lets you write recursive anonymous functions!

Comment on lines +5170 to +5173
(let ((e1 (cond (original-name `(globalref (thismodule) ,final-name))
((and arg-map (symbol? final-name))
(get arg-map final-name final-name))
(else final-name))))
Copy link
Member Author

Choose a reason for hiding this comment

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

This part seems necessary for handling kwcall correctly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants