Skip to content

Commit a046da5

Browse files
authored
lowering: Fix captured vars shadowed by an inner global declaration (#57648)
As discovered in #57547, lowering isn't resolving references to captured variables when a global of the same name is declared in any scope in the current function: ``` julia> let g = 1 function f() let; global g = 2; end; return g # g is not resolved end; f() end ERROR: Found raw symbol in code returned from lowering. Expected all symbols to have been resolved to GlobalRef or slots. ``` `resolve-scopes` correctly detects that we're returning the local, but scope-blocks are removed in this pass. As a result, `analyze-vars-lambda` finds more globals than `resolve-scopes` did when calling `find-global-decls` (which does not peek inside nested scopes) on `f`. `analyze-vars-lambda` then calculates the set of captured variables in `f` to be: ``` captvars = (current_env ∩ free_vars) - (new_staticparams ∪ wrong_globals) ``` so `g` in this case is never captured. This bug was introduced (revealed, maybe) in #57051---the whole resolution step used to happen after lowering, so lowering passes disagreeing on scopes would be less visible. Fix: omit globals from the free variable list in `analyze-vars-lambda` in the first place. This way we don't need to call the scope-block-dependent `find-global-decls`.
1 parent 5419713 commit a046da5

File tree

3 files changed

+83
-24
lines changed

3 files changed

+83
-24
lines changed

doc/src/manual/variables-and-scoping.md

+6-4
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,14 @@ inside of another local scope, the scope it creates is nested inside of all the
136136
local scopes that it appears within, which are all ultimately nested inside of
137137
the global scope of the module in which the code is evaluated. Variables in
138138
outer scopes are visible from any scope they contain — meaning that they can be
139-
read and written in inner scopes — unless there is a local variable with the
140-
same name that "shadows" the outer variable of the same name. This is true even
141-
if the outer local is declared after (in the sense of textually below) an inner
139+
read and written in inner scopes — unless there is a variable with the same name
140+
that "shadows" the outer variable of the same name. This is true even if the
141+
outer local is declared after (in the sense of textually below) an inner
142142
block. When we say that a variable "exists" in a given scope, this means that a
143143
variable by that name exists in any of the scopes that the current scope is
144-
nested inside of, including the current one.
144+
nested inside of, including the current one. If a variable's value is used in a
145+
local scope, but nothing with its name exists in this scope, it is assumed to be
146+
a global.
145147

146148
Some programming languages require explicitly declaring new variables before
147149
using them. Explicit declaration works in Julia too: in any local scope, writing

src/julia-syntax.scm

+13-11
Original file line numberDiff line numberDiff line change
@@ -3357,11 +3357,11 @@
33573357
(define (lambda-all-vars e)
33583358
(append (lam:argnames e) (caddr e)))
33593359

3360-
;; compute set of variables referenced in a lambda but not bound by it
3360+
;; compute set of non-global variables referenced in a lambda but not bound by it
33613361
(define (free-vars- e tab)
33623362
(cond ((or (eq? e UNUSED) (underscore-symbol? e)) tab)
33633363
((symbol? e) (put! tab e #t))
3364-
((and (pair? e) (eq? (car e) 'globalref)) tab)
3364+
((and (pair? e) (memq (car e) '(global globalref))) tab)
33653365
((and (pair? e) (eq? (car e) 'break-block)) (free-vars- (caddr e) tab))
33663366
((and (pair? e) (eq? (car e) 'with-static-parameters)) (free-vars- (cadr e) tab))
33673367
((or (atom? e) (quoted? e)) tab)
@@ -3385,9 +3385,15 @@
33853385
vi)
33863386
tab))
33873387

3388+
;; env: list of vinfo (includes any closure #self#; should not include globals)
3389+
;; captvars: list of vinfo
3390+
;; sp: list of symbol
3391+
;; new-sp: list of symbol (static params declared here)
3392+
;; methsig: `(call (core svec) ...)
3393+
;; tab: table of (name . var-info)
33883394
(define (analyze-vars-lambda e env captvars sp new-sp methsig tab)
33893395
(let* ((args (lam:args e))
3390-
(locl (caddr e))
3396+
(locl (lam:vinfo e))
33913397
(allv (nconc (map arg-name args) locl))
33923398
(fv (let* ((fv (diff (free-vars (lam:body e)) allv))
33933399
;; add variables referenced in declared types for free vars
@@ -3397,27 +3403,23 @@
33973403
fv))))
33983404
(append (diff dv fv) fv)))
33993405
(sig-fv (if methsig (free-vars methsig) '()))
3400-
(glo (find-global-decls (lam:body e)))
34013406
;; make var-info records for vars introduced by this lambda
34023407
(vi (nconc
34033408
(map (lambda (decl) (make-var-info (decl-var decl)))
34043409
args)
34053410
(map make-var-info locl)))
3406-
(capt-sp (filter (lambda (v) (or (and (memq v fv) (not (memq v glo)) (not (memq v new-sp)))
3411+
(capt-sp (filter (lambda (v) (or (and (memq v fv) (not (memq v new-sp)))
34073412
(memq v sig-fv)))
34083413
sp))
34093414
;; captured vars: vars from the environment that occur
34103415
;; in our set of free variables (fv).
34113416
(cv (append (filter (lambda (v) (and (memq (vinfo:name v) fv)
3412-
(not (memq (vinfo:name v) new-sp))
3413-
(not (memq (vinfo:name v) glo))))
3417+
(not (memq (vinfo:name v) new-sp))))
34143418
env)
34153419
(map make-var-info capt-sp)))
34163420
(new-env (append vi
34173421
;; new environment: add our vars
3418-
(filter (lambda (v)
3419-
(and (not (memq (vinfo:name v) allv))
3420-
(not (memq (vinfo:name v) glo))))
3422+
(filter (lambda (v) (not (memq (vinfo:name v) allv)))
34213423
env))))
34223424
(analyze-vars (lam:body e)
34233425
new-env
@@ -4043,7 +4045,7 @@ f(x) = yt(x)
40434045
((atom? e) e)
40444046
(else
40454047
(case (car e)
4046-
((quote top core globalref thismodule lineinfo line break inert module toplevel null true false meta import using) e)
4048+
((quote top core global globalref thismodule lineinfo line break inert module toplevel null true false meta import using) e)
40474049
((toplevel-only)
40484050
;; hack to avoid generating a (method x) expr for struct types
40494051
(if (eq? (cadr e) 'struct)

test/syntax.jl

+64-9
Original file line numberDiff line numberDiff line change
@@ -2417,15 +2417,6 @@ macro a35391(b)
24172417
end
24182418
@test @a35391(0) === (0,)
24192419

2420-
# global declarations from the top level are not inherited by functions.
2421-
# don't allow such a declaration to override an outer local, since it's not
2422-
# clear what it should do.
2423-
@test Meta.lower(Main, :(let
2424-
x = 1
2425-
let
2426-
global x
2427-
end
2428-
end)) == Expr(:error, "`global x`: x is a local variable in its enclosing scope")
24292420
# note: this `begin` block must be at the top level
24302421
_temp_33553 = begin
24312422
global _x_this_remains_undefined
@@ -2437,6 +2428,70 @@ end
24372428
@test _temp_33553 == 2
24382429
@test !@isdefined(_x_this_remains_undefined)
24392430

2431+
module GlobalContainment
2432+
using Test
2433+
@testset "scope of global declarations" begin
2434+
2435+
# global declarations from the top level are not inherited by functions.
2436+
# don't allow such a declaration to override an outer local, since it's not
2437+
# clear what it should do.
2438+
@test Meta.lower(
2439+
Main,
2440+
:(let
2441+
x = 1
2442+
let
2443+
global x
2444+
end
2445+
end)) == Expr(:error, "`global x`: x is a local variable in its enclosing scope")
2446+
2447+
# a declared global can shadow a local in an outer scope
2448+
@test let
2449+
function f()
2450+
g0 = 2
2451+
let; global g0 = 1; end
2452+
a = () -> (global g0 = 1); a();
2453+
return g0
2454+
end
2455+
(f(), g0);
2456+
end === (2, 1)
2457+
@test let
2458+
function f()
2459+
let; global g2 = 1; end;
2460+
let; try; g2 = 2; catch _; end; end;
2461+
end
2462+
(f(), g2)
2463+
end === (2, 1)
2464+
2465+
# an inner global declaration should not interfere with the closure (#57547)
2466+
@test let
2467+
g3 = 1
2468+
function f()
2469+
let; global g3 = 2; end;
2470+
return g3
2471+
end
2472+
f()
2473+
end === 1
2474+
@test_throws UndefVarError let
2475+
function returns_global()
2476+
for i in 1
2477+
global ge = 2
2478+
end
2479+
return ge # local declared below
2480+
end
2481+
ge = returns_global()
2482+
end
2483+
@test let
2484+
function f(x::T) where T
2485+
function g(x)
2486+
let; global T = 1; end
2487+
x::T
2488+
end; g(x)
2489+
end; f(1)
2490+
end === 1
2491+
2492+
end
2493+
end
2494+
24402495
# lowering of adjoint
24412496
@test (1 + im)' == 1 - im
24422497
x = let var"'"(x) = 2x

0 commit comments

Comments
 (0)