diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index ad78221c540c5..cb3cb282f3d6b 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -172,6 +172,22 @@ (define (method-lambda-expr argl body rett) (let ((argl (map arg-name argl)) (body (blockify body))) + ;; If the method body mentions |#self#| but no parameter is called + ;; |#self#|, introduce a local alias so var"#self#" works for + ;; callable objects as it does for ordinary functions. + (let* ((have-self-arg? (memq '|#self#| argl)) + (first (and (pair? argl) (car argl))) + (needs-alias? + (and (not have-self-arg?) + (expr-contains-p + (lambda (x) (eq? x '|#self#|)) + body)))) + (when needs-alias? + (set! body + (insert-after-meta + body + `((local |#self#|) + (= |#self#| ,first)))))) `(lambda ,argl () (scope-block ,(if (equal? rett '(core Any)) diff --git a/test/syntax.jl b/test/syntax.jl index dcd921823d273..44c6ca6a15bcb 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -4351,3 +4351,40 @@ let f = NoSpecClosure.K(1) @test f(2) == 1 @test typeof(f).parameters == Core.svec() end + +# var"#self#" +# regular functions can use var"#self#" to refer to the function itself +regular_func() = var"#self#" +@test regular_func() === regular_func + +# callable structs can also use var"#self#", which will refer to the struct instance +struct CallableStruct + value::Int +end +(obj::CallableStruct)() = var"#self#" +(obj::CallableStruct)(x) = var"#self#".value + x + +let cs = CallableStruct(42) + @test cs() === cs + @test cs(10) === 52 +end + +struct RecursiveCallableStruct; end +(::RecursiveCallableStruct)(n) = n <= 1 ? n : var"#self#"(n-1) + var"#self#"(n-2) + +@test RecursiveCallableStruct()(10) === 55 + +# In closures, var"#self#" should refer to the enclosing function, +# NOT the enclosing struct instance +struct CallableStruct2; end +function (obj::CallableStruct2)() + function inner_func() + var"#self#" + end + inner_func +end + +let cs = CallableStruct2() + @test cs()() === cs() + @test cs()() !== cs +end