-
-
Notifications
You must be signed in to change notification settings - Fork 1
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
Record assignments to global variables and propagate into local scopes #4
Conversation
src/SoftGlobalScope.jl
Outdated
elseif isexpr(ex, :block) || isexpr(ex, :if) | ||
return Expr(ex.head, _softscope.(ex.args, Ref(globals), insertglobal)...) | ||
elseif isexpr(ex, :local) | ||
setdiff!(globals, localvars(ex.args)) # affects globals in surrounding scope! | ||
return ex | ||
elseif insertglobal && ex.head in assignments && ex.args[1] in globals | ||
return Expr(:global, Expr(ex.head, ex.args[1], _softscope(ex.args[2], globals, insertglobal))) | ||
elseif !insertglobal && isexpr(ex, :(=)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't this treat x
as global even if it has been explicitly declared as local
?
e.g. consider
begin
local x
x = 0
for i = 1:10
x += i
end
end
or
let x=1
x=0
for i = 1:10
x += i
end
end
In both cases, you don't want to transform the body of the for
loop to global x += i
, because x
is explicitly local.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably need a set locals
, in addition to globals
, to track local variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the elseif isexpr(ex, :local)
beforehand catches that one
julia> a = 1
1
julia> softscope(Main, :(begin; local a = 2; for i = 1:10; a += 1; end; end))
quote
#= REPL[5]:1 =#
local a = 2
#= REPL[5]:1 =#
for i = 1:10
#= REPL[5]:1 =#
a += 1
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, though subsequent assignments will get caught... OK, so we might need a separate list of locals.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry didn't see your extra comments - browser didn't refresh.
Your first example is problematic but the second example is fine with this PR. In the second example, the let introduces a local scope and so insertglobals
is true -> the assignment is not recorded.
src/SoftGlobalScope.jl
Outdated
if isexpr(ex, :for) || isexpr(ex, :while) | ||
return Expr(ex.head, ex.args[1], _softscope(ex.args[2], copy(globals), true)) | ||
return Expr(ex.head, ex.args[1], _softscope(ex.args[2], globals, copy(locals), true)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs copy(globals)
as well for global x
statements not to affect the surroundsing scope, no? Similarly for try
and let
and any other expression that introduces a new scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be good to have a test for this, e.g. in
for i = 1:10
for j = 1:3; global x += 1; end
x = 3
end
the x = 3
statement should not be transformed to global x = 3
(unless x
was already in the globals
list).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not quite sure what you mean. Can you give an example?
As far as I can see, once you've added a global it's there permanently for all future scopes. The only question is whether there is a local variable shadowing it or not. As such, manipulations of the locals
should be sufficient. (BTW: there should be a check for global x
in the code to add new globals directly but that's not in the original code either.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies, browser not refreshing again...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, your test passes fine at the moment! Though I can think of other places it won't.
return Expr(ex.head, _softscope(ex.args[1], globals, insertglobal), | ||
_softscope(ex.args[2], letglobals, true)) | ||
letlocals = union(locals, localvars(ex.args[1])) | ||
return Expr(ex.head, ex.args[1], _softscope(ex.args[2], copy(globals), letlocals, true)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that args[1]
is problematic here. e.g.
let x = (a += 1); end
needs a global a
(assuming :a in globals
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I know about this - it's mentioned in my original PR text above. The original code was also broken in this regard and so this is no less bad 😄 Plus this version doesn't have the other bug that was in the original (again, see above).
elseif insertglobal && ex.head in assignments && ex.args[1] in globals && !(ex.args[1] in locals) | ||
return Expr(:global, Expr(ex.head, ex.args[1], _softscope(ex.args[2], globals, locals, insertglobal))) | ||
elseif !insertglobal && isexpr(ex, :(=)) # only assignments in the global scope need to be considered | ||
union!(globals, localvars(ex)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If ex.head
is a Symbol
or a tuple
expression, then you also want to call softscope_
on the rhs, e.g. to handle things like x = y = 3
.
return ex | ||
elseif insertglobal && ex.head in assignments && ex.args[1] in globals | ||
return Expr(:global, Expr(ex.head, ex.args[1], _softscope(ex.args[2], globals, insertglobal))) | ||
else | ||
return ex |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that return ex
here is rather conservative. e.g. it will miss things like sqrt(begin; for i=0:10; a+=1; end; a; end)
that have nested blocks inside function calls, on the rhs of assignment expressions, etcetera.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True but I think that should be the subject of another PR. As it stands, this has the same behaviour as the previous code.
return Expr(ex.head, _softscope(ex.args[1], globals, insertglobal), | ||
_softscope(ex.args[2], letglobals, true)) | ||
letlocals = union(locals, localvars(ex.args[1])) | ||
return Expr(ex.head, ex.args[1], _softscope(ex.args[2], copy(globals), letlocals, true)) | ||
elseif isexpr(ex, :block) || isexpr(ex, :if) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need || isexpr(ex, :toplevel)
here too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(This was a problem in the original code too.)
Can you file an issue for the other problems noted above? |
Will do - sorry don't have any more time today to do any more. |
This PR allows recording of assignments done with blocks to allow statements like
to work even when
aa
has not been previously defined.The key changes are to add any assigned variables to the
globals
variable wheneverinsertglobal
is false (i.e., when in the global scope). Other assignment operators (+= and friends) are ignored as they will result in aUndefVarError
if the variable isn't already a global variable.One thing to note is that I've changed the behaviour of how
let
blocks were handled. Previously thelet
assignments (in contrast to thelet
body) were softscoped the inheritedinsertglobal
value but (a) I don't understand why that is, and (b) it gives errors in cases likewhere
a
is a previously defined global.The new way that let blocks are handled is to not softscope the assignments on the principle that it's pretty rare to try to assign to a global variable within the assignment statement of a
let
block, whereas the example above is not at all uncommon (particularly for programmatically defining functions).It should be possible to do this properly but I'm not quite there yet on that one.