Skip to content

Commit

Permalink
Fix scope of hoisted signature-local variables
Browse files Browse the repository at this point in the history
When we declare inner methods, e.g. the `f` in

```
function fs()
   f(lhs::Integer) = 1
   f(lhs::Integer, rhs::(local x=Integer; x)) = 2
   return f
end
```

we must hoist the definition of the (appropriately mangled) generic
function `f` to top-level, including all variables that were used
in the signature definition of `f`. This situation is a bit
unique in the language because it uses inner function scope, but gets
executed in toplevel scope. For example, you're not allowed to use a local
of the inner function in the signature definition:

```
julia> function fs()
          local x=Integer
          f(lhs::Integer, rhs::x) = 2
          return f
       end
ERROR: syntax: local variable x cannot be used in closure declaration
Stacktrace:
 [1] top-level scope
   @ REPL[3]:1
```

In particular, the restriction is signature-local:
```
julia> function fs()
          f(rhs::(local x=Integer; x)) = 1
          f(lhs::Integer, rhs::x) = 2
          return f
       end
ERROR: syntax: local variable x cannot be used in closure declaration
Stacktrace:
 [1] top-level scope
   @ REPL[4]:1
```

There's a special intermediate form `moved-local` that gets generated
for this definition. In c6c3d72, this form
stopped getting generated for certain inner methods. I suspect this
happened because of the incorrect assumption that the set of moved
locals is being computed over all signatures, rather than being a
per-signature property.

The result of all of this was that this is one of the few places
where lowering still generated a symbol as the lhs of an assignment
for a global (instead of globalref), because the code that generates
the assignment assumes it's a local, but the later pass doesn't know this.
Because we still retain the code for this from before we started using globalref
consistently, this wasn't generally causing a problems, except possibly leaking
a global (or potentially assigning to a global when this wasn't intended).
However, in follow on work, I want to make use of knowing whether the LHS is a
global or local in lowering, so this was causing me trouble.

Fix all of this by putting back the `moved-local` where it was dropped.

Fixes #56711
  • Loading branch information
Keno committed Nov 29, 2024
1 parent c1f806d commit 1001750
Show file tree
Hide file tree
Showing 2 changed files with 9 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -4279,6 +4279,7 @@ f(x) = yt(x)
(if (or exists (and short (pair? alldefs)))
`(toplevel-butfirst
(null)
,@(map (lambda (v) `(moved-local ,v)) moved-vars)
,@sp-inits
,@mk-method
(latestworld))
Expand Down
8 changes: 8 additions & 0 deletions test/syntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4033,3 +4033,11 @@ end
@test isa(create_inner_f_no_methods(), Function)
@test length(methods(create_inner_f_no_methods())) == 0
@test Base.invoke_in_world(first(methods(create_inner_f_one_method)).primary_world, create_inner_f_one_method()) == 1

# Issue 56711 - Scope of signature hoisting
function fs56711()
f(lhs::Integer) = 1
f(lhs::Integer, rhs::(local x_should_not_be_defined=Integer; x_should_not_be_defined)) = 2
return f
end
@test !@isdefined(x_should_not_be_defined)

0 comments on commit 1001750

Please sign in to comment.