-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
more predictable global binding resolution #22984
Conversation
Of course, CI is failing because a couple of deprecations were written to depend on the old behavior of #17387 and will need an minor update to Compat first to disable this fix in one place in the code. |
test/core.jl
Outdated
# issue 18933 | ||
module GlobalDef18933 | ||
using Base.Test | ||
f() = (global sin; nothing) |
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.
Hm, this is kind of a weird case. Normally global sin
at the toplevel would create a binding in the current module. But I see that since f
doesn't assign to sin
, maybe it shouldn't here. I guess this behavior is probably ok.
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, it is rather awkward that we use global
to mean different operations depending on the 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.
even worse, before we weren't even consistent about what this syntax did. for example:
if true
global sin
nothing
end
did not create a new global binding, but merely annotated that if a variable named sin
was used, that its scope is global.
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.
Ow, good point.
Should add a test for exactly that case.
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.
It's a minor breaking change (but I suspect it will break a handful of packages anyways), but this PR means that:
let
global function f(); return 1; end
end
Now expands to
global f
f() = 1
And thus will not add to an existing binding from import
, but will instead give an overwrite notice.
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.
It's possible we'll require explicit qualification for extension anyway, so this might be moot. However, in the meantime it's possible that global x
should not overwrite "hard-imported" bindings. That will be less breaking, since currently it gives a warning.
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.
Sure. That operation gives a warning now, so presumably it would be less breaking for it to be ignored (have global x
mean more consistently the statement "x is a global" rather than the being sometimes the command "make x global").
((ssavalue? var) | ||
`(= ,var ,rhs0)) | ||
(else | ||
(error (string "invalid assignment location \"" (deparse var) "\""))))) |
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 looks like a good refactoring. Am I right that there's no functional change except for this error?
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 was thinking of using this code-path to test for globals, but ended up deciding to put it elsewhere.
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.
maybe put this in a separate PR, especially if it's a completely independent change?
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.
It's largely just a re-indentation to accommodate printing the error message. It seems that lisp just usually makes trivial edits require re-indenting the entire function :(
src/julia-syntax.scm
Outdated
(if (and (pair? e) (eq? (car e) 'block)) | ||
e | ||
`(block ,e)) | ||
glob-decl))))) |
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.
I'm not totally convinced it's worth adding this code instead of just looking for global assignments in jl_resolve_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.
This moves the global decls from local scope (where the syntax does not inherently create new bindings), to global scope (where it does). I'm generally inclined to do all scope and syntax lowering here, rather than implementing any part of it in post-processing code.
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.
Ok, I'm on board.
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.
Also, while having jl_resolve_globals
do this will mostly work, it's still broken if the global code has any other side-effects, since the current scope rules can't preserve the happens-before relationship the user may have intended. Ignoring the trivial cases like code that uses eval
or include
, another example of where we must give the wrong behavior is if import
is in the same block as global
. (so import Foo.bar; global bar() = 3
will execute in the wrong order, for example)
Make sure that @nanosoldier |
9a0073d
to
8a71761
Compare
NEWS.md
Outdated
* The `global` keyword now always introduces a new binding when appearing in toplevel (global) scope. | ||
Whereas, previously, embedding it in a block (such as `begin; global sin; nothing; end`) would change its meaning. | ||
Additionally, the new bindings are now created before the statement is executed. | ||
For example, `f() = (global sin = rand(); nothing)` will now reliably shadow the `Base.sin` function, |
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 comma at the end of this line
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.
The comma makes this a dependent clause, which I think is preferable here (the additional statements are present for additional clarity, but aren't as uniformly true as the first statement).
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.
"with a new, undefined sin
variable" is an incomplete fragment with no clear subject of its own - it is directly describing the shadowing and doesn't stand on its own
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.
the comma here does not make sense grammatically and should be removed.
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 is not important
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.
these are the release notes, which people will read, they should read clearly
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.
https://en.wikipedia.org/wiki/Relative_clause#Restrictive_and_non-restrictive
If it was a complete fragment, this would be a comma splice. The rest of the sentence, on the other hand, is complete and stands on its own.
doc/make.jl
Outdated
@@ -1,152 +0,0 @@ | |||
# Install dependencies needed to build the documentation. |
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.
put this back
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.
fortunately, it looks like AppVeyor won't give this PR a passing mark without this (but FreeBSD will). Need to merge a couple PRs to the doc packages first.
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @ararslan |
this ensures that we only introduce package-local bindings for values that we define in this package ref JuliaLang/julia#22984
this ensures that we only introduce package-local bindings for values that we define in this package ref JuliaLang/julia#22984
test/core.jl
Outdated
f() = (global sin; nothing) | ||
g() = (global cos; cos = 2; nothing) | ||
@test @isdefined sin | ||
@test !@isdefined cos |
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.
It would be handy here to have some way to test what a binding refers to without dereferencing it, as in resolvebinding(:sin) == :(Base.sin)
and resolvebinding(:cos) == :cos
, but that may be beyond the scope of this PR.
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 it has a value, @which
. I could add some tests here with it for additional clarity / verification, but I think it's basically already covered.
test/core.jl
Outdated
nothing | ||
end | ||
@test !@isdefined sincos | ||
@test isdefined(Base, :sincos) |
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.
I find this last one slightly surprising, but I suppose it fall naturally out of if
not introducing scope. Should this also test the same things with assignment (without the let
)?
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 think it's a natural outcome of scope rules (one way to "fix" this might be to remove global scope, ala #19324).
But I'm going to change the way this works in an upcoming commit, which may reduce its impact on typical code.
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.
Jeff and I have discussed some of the implications of this fix for the global
keyword, and have decided we should change the meaning of the keyword slightly, affecting the error case.
Currently, global
means "make a new global" when used as a toplevel statement, but means "there is a global" when used anywhere else. Typically these two statements actually end up doing the same action, and thus differ only in the warning / error case.
This PR had been written to fix the keyword to mean "make a new global" when used in global scope or when there is an assignment to a global in local scope. But to otherwise mean "there is a global".
We have instead decided it should always mean "there is a global". This mainly just impacts cases where we currently give a warning ("WARNING: imported binding for sin overwritten in module Main"), and instead makes that operation a no-op. Because that means bindings can no longer be deleted (which was invalid anyways for various reasons), assignment to an existing global imported from another module will now emit an error ("cannot assign variables in other modules") rather than emitting a warning.
test/core.jl
Outdated
f() = (global sin; nothing) | ||
g() = (global cos; cos = 2; nothing) | ||
@test @isdefined sin | ||
@test !@isdefined cos |
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 it has a value, @which
. I could add some tests here with it for additional clarity / verification, but I think it's basically already covered.
test/core.jl
Outdated
nothing | ||
end | ||
@test !@isdefined sincos | ||
@test isdefined(Base, :sincos) |
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 think it's a natural outcome of scope rules (one way to "fix" this might be to remove global scope, ala #19324).
But I'm going to change the way this works in an upcoming commit, which may reduce its impact on typical code.
src/module.c
Outdated
else { | ||
jl_binding_t *b2 = jl_get_binding(b->owner, var); | ||
if (b2 == NULL || b2->value == NULL) | ||
jl_errorf("invalid method definition: imported function %s.%s does not exist", jl_symbol_name(b->owner->name), jl_symbol_name(var)); |
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.
wrap long line
I've been kicking the tires on this a bit and it seems good. 👍 |
This changes the meaning of `global` from being a directive when used at toplevel, which forces the introduction of a new global when used in certain contexts, to always being just an scope annotation that there should exist a global binding for the given name. fix #18933 fix #17387 (for the syntactic case)
This takes any syntactic assignment to a global inside a function and ensure we create the binding immediately.EDIT: The design of this PR has changed, although the net effect is generally much less breaking (since now the divergence of the new vs. existing behavior was previously typically an error case):
fix #18933
fix #17387