diff --git a/NEWS.md b/NEWS.md index b25a8940475263..0dc2f12ec5b572 100644 --- a/NEWS.md +++ b/NEWS.md @@ -22,6 +22,13 @@ Language changes * The parsing of `1<<2*3` as `1<<(2*3)` is deprecated, and will change to `(1<<2)*3` in a future version ([#13079]). + * 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, + with a new, undefined `sin` variable. ([#22984]). + + Breaking changes ---------------- diff --git a/base/poll.jl b/base/poll.jl index e53b5892dd1bfa..d7d53dfef67629 100644 --- a/base/poll.jl +++ b/base/poll.jl @@ -145,8 +145,7 @@ mutable struct _FDWatcher end end - global uvfinalize - function uvfinalize(t::_FDWatcher) + function Base.uvfinalize(t::_FDWatcher) if t.handle != C_NULL disassociate_julia_struct(t) ccall(:jl_close_uv, Void, (Ptr{Void},), t.handle) diff --git a/doc/make.jl b/doc/make.jl index ad966f96d51cb3..e69de29bb2d1d6 100644 --- a/doc/make.jl +++ b/doc/make.jl @@ -1,152 +0,0 @@ -# Install dependencies needed to build the documentation. -ENV["JULIA_PKGDIR"] = joinpath(@__DIR__, "deps") -Pkg.init() -cp(joinpath(@__DIR__, "REQUIRE"), Pkg.dir("REQUIRE"); remove_destination = true) -Pkg.update() -Pkg.resolve() - -using Documenter - -# Include the `build_sysimg` file. - -baremodule GenStdLib end -isdefined(:build_sysimg) || @eval module BuildSysImg - include(joinpath(@__DIR__, "..", "contrib", "build_sysimg.jl")) -end - -# Documenter Setup. - -const PAGES = [ - "Home" => "index.md", - "Manual" => [ - "manual/introduction.md", - "manual/getting-started.md", - "manual/variables.md", - "manual/integers-and-floating-point-numbers.md", - "manual/mathematical-operations.md", - "manual/complex-and-rational-numbers.md", - "manual/strings.md", - "manual/functions.md", - "manual/control-flow.md", - "manual/variables-and-scoping.md", - "manual/types.md", - "manual/methods.md", - "manual/constructors.md", - "manual/conversion-and-promotion.md", - "manual/interfaces.md", - "manual/modules.md", - "manual/documentation.md", - "manual/metaprogramming.md", - "manual/arrays.md", - "manual/linear-algebra.md", - "manual/networking-and-streams.md", - "manual/parallel-computing.md", - "manual/dates.md", - "manual/interacting-with-julia.md", - "manual/running-external-programs.md", - "manual/calling-c-and-fortran-code.md", - "manual/handling-operating-system-variation.md", - "manual/environment-variables.md", - "manual/embedding.md", - "manual/packages.md", - "manual/profile.md", - "manual/stacktraces.md", - "manual/performance-tips.md", - "manual/workflow-tips.md", - "manual/style-guide.md", - "manual/faq.md", - "manual/noteworthy-differences.md", - "manual/unicode-input.md", - ], - "Standard Library" => [ - "stdlib/base.md", - "stdlib/collections.md", - "stdlib/math.md", - "stdlib/numbers.md", - "stdlib/strings.md", - "stdlib/arrays.md", - "stdlib/parallel.md", - "stdlib/linalg.md", - "stdlib/constants.md", - "stdlib/file.md", - "stdlib/io-network.md", - "stdlib/punctuation.md", - "stdlib/sort.md", - "stdlib/pkg.md", - "stdlib/dates.md", - "stdlib/iterators.md", - "stdlib/test.md", - "stdlib/c.md", - "stdlib/libc.md", - "stdlib/libdl.md", - "stdlib/profile.md", - "stdlib/stacktraces.md", - "stdlib/simd-types.md", - ], - "Developer Documentation" => [ - "devdocs/reflection.md", - "Documentation of Julia's Internals" => [ - "devdocs/init.md", - "devdocs/ast.md", - "devdocs/types.md", - "devdocs/object.md", - "devdocs/eval.md", - "devdocs/callconv.md", - "devdocs/compiler.md", - "devdocs/functions.md", - "devdocs/cartesian.md", - "devdocs/meta.md", - "devdocs/subarrays.md", - "devdocs/sysimg.md", - "devdocs/llvm.md", - "devdocs/stdio.md", - "devdocs/boundscheck.md", - "devdocs/locks.md", - "devdocs/offset-arrays.md", - "devdocs/libgit2.md", - "devdocs/require.md", - "devdocs/inference.md", - ], - "Developing/debugging Julia's C code" => [ - "devdocs/backtraces.md", - "devdocs/debuggingtips.md", - "devdocs/valgrind.md", - "devdocs/sanitizers.md", - ] - ], -] - -makedocs( - build = joinpath(pwd(), "_build/html/en"), - modules = [Base, Core, BuildSysImg], - clean = false, - doctest = "doctest" in ARGS, - linkcheck = "linkcheck" in ARGS, - linkcheck_ignore = ["https://bugs.kde.org/show_bug.cgi?id=136779"], # fails to load from nanosoldier? - strict = true, - checkdocs = :none, - format = "pdf" in ARGS ? :latex : :html, - sitename = "The Julia Language", - authors = "The Julia Project", - analytics = "UA-28835595-6", - pages = PAGES, - html_prettyurls = ("deploy" in ARGS), -) - -if "deploy" in ARGS - # Only deploy docs from 64bit Linux to avoid committing multiple versions of the same - # docs from different workers. - (Sys.ARCH === :x86_64 && Sys.KERNEL === :Linux) || return - - # Since the `.travis.yml` config specifies `language: cpp` and not `language: julia` we - # need to manually set the version of Julia that we are deploying the docs from. - ENV["TRAVIS_JULIA_VERSION"] = "nightly" - - deploydocs( - repo = "github.com/JuliaLang/julia.git", - target = "_build/html/en", - dirname = "en", - deps = nothing, - make = nothing, - ) -end diff --git a/src/interpreter.c b/src/interpreter.c index 19f4c576e00f10..319abe1da1af61 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -319,22 +319,6 @@ static jl_value_t *eval(jl_value_t *e, interpreter_state *s) jl_declare_constant(b); return (jl_value_t*)jl_nothing; } - else if (ex->head == global_sym) { - // create uninitialized mutable binding for "global x" decl - // TODO: handle type decls - size_t i, l = jl_array_len(ex->args); - for (i = 0; i < l; i++) { - jl_sym_t *gsym = (jl_sym_t*)args[i]; - jl_module_t *gmodu = modu; - if (jl_is_globalref(gsym)) { - gmodu = jl_globalref_mod(gsym); - gsym = jl_globalref_name(gsym); - } - assert(jl_is_symbol(gsym)); - jl_get_binding_wr(gmodu, gsym); - } - return (jl_value_t*)jl_nothing; - } else if (ex->head == abstracttype_sym) { if (inside_typedef) jl_error("cannot eval a new abstract type definition while defining another type"); diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 973874d4a864ee..52843eef551dd7 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -2641,8 +2641,9 @@ (define (free-vars e) (table.keys (free-vars- e (table)))) -(define (analyze-vars-lambda e env captvars sp new-sp) +(define (analyze-vars-lambda e env captvars sp new-sp glob-assign) (let* ((args (lam:args e)) + (glob-assign (if (null? args) (table) glob-assign)) (locl (caddr e)) (allv (nconc (map arg-name args) locl)) (fv (let* ((fv (diff (free-vars (lam:body e)) allv)) @@ -2674,7 +2675,14 @@ (and (not (memq (vinfo:name v) allv)) (not (memq (vinfo:name v) glo)))) env)) - cv (delete-duplicates (append new-sp sp))) + cv + (delete-duplicates (append new-sp sp)) + glob-assign) + ;; if we collected any assignments to globals + ;; annotate them now at the toplevel + (if (null? args) + (let ((glob-decl (map (lambda (e) `(global ,e)) (table.keys glob-assign)))) + (set-car! (cdddr e) (insert-after-meta (lam:body e) glob-decl)))) ;; mark all the vars we capture as captured (for-each (lambda (v) (vinfo:set-capt! v #t)) cv) @@ -2689,7 +2697,7 @@ ;; in-place to ;; (var-info-lst captured-var-infos ssavalues static_params) ;; where var-info-lst is a list of var-info records -(define (analyze-vars e env captvars sp) +(define (analyze-vars e env captvars sp glob-assign) (if (or (atom? e) (quoted? e)) e (case (car e) @@ -2697,18 +2705,23 @@ (let ((vi (var-info-for (cadr e) env))) (vinfo:set-never-undef! vi #t))) ((=) - (let ((vi (var-info-for (cadr e) env))) - (if vi - (begin (if (vinfo:asgn vi) - (vinfo:set-sa! vi #f) - (vinfo:set-sa! vi #t)) - (vinfo:set-asgn! vi #t)))) - (analyze-vars (caddr e) env captvars sp)) + (if (not (ssavalue? (cadr e))) + (let ((vi (and (symbol? (cadr e)) (var-info-for (cadr e) env)))) + (if vi ; if local or captured + (begin (if (vinfo:asgn vi) + (vinfo:set-sa! vi #f) + (vinfo:set-sa! vi #t)) + (vinfo:set-asgn! vi #t)) + (if (and (pair? (cadr e)) (eq? (caadr e) 'outerref)) + (if (not (memq (cadadr e) sp)) ; if not a sparam + (put! glob-assign (cadadr e) #t)) ; it's a global + (put! glob-assign (cadr e) #t))))) ; symbol or global ref + (analyze-vars (caddr e) env captvars sp glob-assign)) ((call) (let ((vi (var-info-for (cadr e) env))) (if vi (vinfo:set-called! vi #t)) - (for-each (lambda (x) (analyze-vars x env captvars sp)) + (for-each (lambda (x) (analyze-vars x env captvars sp glob-assign)) (cdr e)))) ((decl) ;; handle var::T declaration by storing the type in the var-info @@ -2723,12 +2736,13 @@ "\" declared in inner scope"))) (vinfo:set-type! vi (caddr e)))))) ((lambda) - (analyze-vars-lambda e env captvars sp '())) + (analyze-vars-lambda e env captvars sp '() glob-assign)) ((with-static-parameters) ;; (with-static-parameters func_expr sp_1 sp_2 ...) (assert (eq? (car (cadr e)) 'lambda)) (analyze-vars-lambda (cadr e) env captvars sp - (cddr e))) + (cddr e) + glob-assign)) ((method) (if (length= e 2) (let ((vi (var-info-for (method-expr-name e) env))) @@ -2738,15 +2752,28 @@ (vinfo:set-sa! vi #t)) (vinfo:set-asgn! vi #t))) e) - (begin (analyze-vars (caddr e) env captvars sp) + (begin (analyze-vars (caddr e) env captvars sp glob-assign) (assert (eq? (car (cadddr e)) 'lambda)) (analyze-vars-lambda (cadddr e) env captvars sp - (method-expr-static-parameters e))))) + (method-expr-static-parameters e) + glob-assign)))) ((module toplevel) e) - (else (for-each (lambda (x) (analyze-vars x env captvars sp)) + (else (for-each (lambda (x) (analyze-vars x env captvars sp glob-assign)) (cdr e)))))) -(define (analyze-variables! e) (analyze-vars e '() '() '()) e) +(define (analyze-variables! e) + (let ((glob-assign (table))) + (analyze-vars e '() '() '() glob-assign) + ;; if we collected any assignments to globals + ;; annotate them now at the toplevel + (let ((glob-decl (map (lambda (e) `(global ,e)) (table.keys glob-assign)))) + (if (null? glob-decl) + e + (insert-after-meta + (if (and (pair? e) (eq? (car e) 'block)) + e + `(block ,e)) + glob-decl))))) ;; pass 4: closure conversion @@ -2841,35 +2868,45 @@ f(x) = yt(x) ;; when doing this, the original value needs to be preserved, to ;; ensure the expression `a=b` always returns exactly `b`. (define (convert-assignment var rhs0 fname lam interp) - (let* ((vi (assq var (car (lam:vinfo lam)))) - (cv (assq var (cadr (lam:vinfo lam)))) - (vt (or (and vi (vinfo:type vi)) - (and cv (vinfo:type cv)) - '(core Any))) - (closed (and cv (vinfo:asgn cv) (vinfo:capt cv))) - (capt (and vi (vinfo:asgn vi) (vinfo:capt vi)))) - (if (and (not closed) (not capt) (equal? vt '(core Any))) - `(= ,var ,rhs0) - (let* ((rhs1 (if (or (ssavalue? rhs0) (simple-atom? rhs0) - (equal? rhs0 '(the_exception))) - rhs0 - (make-ssavalue))) - (rhs (if (equal? vt '(core Any)) - rhs1 - (convert-for-type-decl rhs1 (cl-convert vt fname lam #f #f interp)))) - (ex (cond (closed `(call (core setfield!) - ,(if interp - `($ ,var) - `(call (core getfield) ,fname (inert ,var))) - (inert contents) - ,rhs)) - (capt `(call (core setfield!) ,var (inert contents) ,rhs)) - (else `(= ,var ,rhs))))) - (if (eq? rhs1 rhs0) - `(block ,ex ,rhs0) - `(block (= ,rhs1 ,rhs0) - ,ex - ,rhs1)))))) + (cond + ((symbol? var) + (let* ((vi (assq var (car (lam:vinfo lam)))) + (cv (assq var (cadr (lam:vinfo lam)))) + (vt (or (and vi (vinfo:type vi)) + (and cv (vinfo:type cv)) + '(core Any))) + (closed (and cv (vinfo:asgn cv) (vinfo:capt cv))) + (capt (and vi (vinfo:asgn vi) (vinfo:capt vi)))) + (if (and (not closed) (not capt) (equal? vt '(core Any))) + `(= ,var ,rhs0) + (let* ((rhs1 (if (or (ssavalue? rhs0) (simple-atom? rhs0) + (equal? rhs0 '(the_exception))) + rhs0 + (make-ssavalue))) + (rhs (if (equal? vt '(core Any)) + rhs1 + (convert-for-type-decl rhs1 (cl-convert vt fname lam #f #f interp)))) + (ex (cond (closed `(call (core setfield!) + ,(if interp + `($ ,var) + `(call (core getfield) ,fname (inert ,var))) + (inert contents) + ,rhs)) + (capt `(call (core setfield!) ,var (inert contents) ,rhs)) + (else `(= ,var ,rhs))))) + (if (eq? rhs1 rhs0) + `(block ,ex ,rhs0) + `(block (= ,rhs1 ,rhs0) + ,ex + ,rhs1)))))) + ((and (pair? var) (or (eq? (car var) 'outerref) + (eq? (car var) 'globalref))) + + `(= ,var ,rhs0)) + ((ssavalue? var) + `(= ,var ,rhs0)) + (else + (error (string "invalid assignment location \"" (deparse var) "\""))))) ;; replace leading (function) argument type with `typ` (define (fix-function-arg-type te typ iskw namemap type-sp) @@ -3056,9 +3093,7 @@ f(x) = yt(x) ((=) (let ((var (cadr e)) (rhs (cl-convert (caddr e) fname lam namemap toplevel interp))) - (if (ssavalue? var) - `(= ,var ,rhs) - (convert-assignment var rhs fname lam interp)))) + (convert-assignment var rhs fname lam interp))) ((local-def) ;; make new Box for local declaration of defined variable (let ((vi (assq (cadr e) (car (lam:vinfo lam))))) (if (and vi (vinfo:asgn vi) (vinfo:capt vi)) @@ -3100,10 +3135,10 @@ f(x) = yt(x) (lam2 (if short #f (cadddr e))) (vis (if short '(() () ()) (lam:vinfo lam2))) (cvs (map car (cadr vis))) - (local? (lambda (s) (and (symbol? s) + (local? (lambda (s) (and lam (symbol? s) (or (assq s (car (lam:vinfo lam))) (assq s (cadr (lam:vinfo lam))))))) - (local (and lam (local? name))) + (local (local? name)) (sig (and (not short) (caddr e))) (sp-inits (if (or short (not (eq? (car sig) 'block))) '() @@ -3180,7 +3215,7 @@ f(x) = yt(x) (and (symbol? s) (not (eq? name s)) (not (memq s capt-sp)) - (or ;(local? s) ; TODO: make this work for local variables too? + (or ;(local? s) ; TODO: error for local variables (memq s (lam:sp lam))))))) (caddr methdef) (lambda (e) (cadr e))))) @@ -3306,7 +3341,8 @@ f(x) = yt(x) ;; numbered slots (or be simple immediate values), and then those will be the ;; only possible returned values. (define (compile-body e vi lam) - (let ((code '()) + (let ((code '()) ;; statements (emitted in reverse order) + (glob-decl '()) ;; global decls will be collected in the prelude to code so they execute first (filename 'none) (first-line #t) (current-loc #f) @@ -3614,6 +3650,7 @@ f(x) = yt(x) (if (var-info-for vname vi) ;; issue #7264 (error (string "`global " vname "`: " vname " is local variable in the enclosing scope")) + (if (null? (lam:args lam)) (set! glob-decl (cons e glob-decl))) ;; keep global decl in thunks #f))) ((local-def) #f) ((local) #f) @@ -3699,13 +3736,13 @@ f(x) = yt(x) (body (cons 'body (filter (lambda (e) (not (and (pair? e) (eq? (car e) 'newvar) (has? di (cadr e))))) - stmts)))) - (if arg-map - (insert-after-meta - body - (table.foldl (lambda (k v lst) (cons `(= ,v ,k) lst)) - '() arg-map)) - body)))) + stmts))) + (prelude (if arg-map + (append! glob-decl + (table.foldl (lambda (k v lst) (cons `(= ,v ,k) lst)) + '() arg-map)) + glob-decl))) + (insert-after-meta body prelude)))) ;; find newvar nodes that are unnecessary because (1) the variable is not ;; captured, and (2) the variable is assigned before any branches. diff --git a/src/method.c b/src/method.c index b5f5baf0c57987..f852fbcaefb4b4 100644 --- a/src/method.c +++ b/src/method.c @@ -25,8 +25,14 @@ jl_value_t *jl_resolve_globals(jl_value_t *expr, jl_module_t *module, jl_svec_t } else if (jl_is_expr(expr)) { jl_expr_t *e = (jl_expr_t*)expr; + if (e->head == global_sym) { + // execute the side-effects of "global x" decl immediately: + // creates uninitialized mutable binding in module for each global + jl_toplevel_eval_flex(module, expr, 0, 1); + expr = jl_nothing; + } if (jl_is_toplevel_only_expr(expr) || e->head == const_sym || e->head == copyast_sym || - e->head == global_sym || e->head == quote_sym || e->head == inert_sym || + e->head == quote_sym || e->head == inert_sym || e->head == line_sym || e->head == meta_sym || e->head == inbounds_sym || e->head == boundscheck_sym || e->head == simdloop_sym) { // ignore these diff --git a/src/toplevel.c b/src/toplevel.c index c90f335ea8b116..65ccbf36e8c426 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -442,6 +442,7 @@ int jl_is_toplevel_only_expr(jl_value_t *e) ((jl_expr_t*)e)->head == using_sym || ((jl_expr_t*)e)->head == export_sym || ((jl_expr_t*)e)->head == thunk_sym || + ((jl_expr_t*)e)->head == global_sym || ((jl_expr_t*)e)->head == toplevel_sym); } @@ -530,9 +531,32 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int e return jl_nothing; } else if (ex->head == line_sym) { - jl_lineno = jl_unbox_long(jl_exprarg(ex,0)); + jl_lineno = jl_unbox_long(jl_exprarg(ex, 0)); return jl_nothing; } + else if (ex->head == global_sym) { + // create uninitialized mutable binding for "global x" decl + size_t i, l = jl_array_len(ex->args); + for (i = 0; i < l; i++) { + jl_value_t *a = jl_exprarg(ex, i); + if (!jl_is_symbol(a) && !jl_is_globalref(a)) + break; + } + if (i == l) { + for (i = 0; i < l; i++) { + jl_sym_t *gs = (jl_sym_t*)jl_exprarg(ex, i); + jl_module_t *gm = m; + if (jl_is_globalref(gs)) { + gm = jl_globalref_mod(gs); + gs = jl_globalref_name(gs); + } + assert(jl_is_symbol(gs)); + jl_get_binding_wr(gm, gs); + } + return jl_nothing; + } + // fall-through to expand to normalize the syntax + } jl_method_instance_t *li = NULL; jl_value_t *result; diff --git a/test/codegen.jl b/test/codegen.jl index e03086e5e3ed56..48ee00433afaff 100644 --- a/test/codegen.jl +++ b/test/codegen.jl @@ -167,19 +167,22 @@ end let was_gced = false @noinline make_tuple(x) = tuple(x) @noinline use(x) = ccall(:jl_breakpoint, Void, ()) - @noinline assert_not_gced() = @assert !was_gced + @noinline assert_not_gced() = @test !was_gced function foo22770() b = Ref(2) - finalizer(b, x->(global was_gced; was_gced=true)) + finalizer(b, x -> was_gced = true) y = make_tuple(b) x = y[1] a = Ref(1) use(x); use(a); use(y) c = Ref(3) - gc(); assert_not_gced(); + gc() + assert_not_gced() use(x) use(c) end foo22770() + gc() + @test was_gced end diff --git a/test/core.jl b/test/core.jl index 6490908963e0e4..7c8b44aca29726 100644 --- a/test/core.jl +++ b/test/core.jl @@ -5089,3 +5089,34 @@ m22929_2.x = m22929_1 @test !isdefined_22929_x(m22929_1) @test isdefined_22929_1(m22929_2) @test isdefined_22929_x(m22929_2) + +# issue 18933 +module GlobalDef18933 + using Base.Test + # test that global declaration vs assignment operates correctly in local scope + f() = (global sin; nothing) + g() = (global cos; cos = 2; nothing) + @test @isdefined sin + @test !@isdefined cos + f() + g() + @test @isdefined cos + @test sin === Base.sin + @test cos === 2 + # test that function definitions declared global + # introduce a new, local global + # and do so prior to the evaluation of the expression + let + @test !@isdefined tan + global tan() = nothing + @test @isdefined tan + @test tan() === nothing + end + # test that global declaration side-effects ignore conditionals + if false + global sincos + nothing + end + @test !@isdefined sincos + @test isdefined(Base, :sincos) +end diff --git a/test/distributed_exec.jl b/test/distributed_exec.jl index 7226a5c0a83f63..ad9cacec0c20f2 100644 --- a/test/distributed_exec.jl +++ b/test/distributed_exec.jl @@ -603,7 +603,8 @@ ntasks = 10 rr_list = [Channel(1) for x in 1:ntasks] for rr in rr_list - let rr=rr + local rr # correct the scope of rr in for loop + let rr = rr # and get a new binding on each iteration @async try for i in 1:10 a = rand(2*10^5)