Skip to content

Commit d31e055

Browse files
committed
remove implicitly inherited globals
fix #19324
1 parent 228c554 commit d31e055

File tree

3 files changed

+167
-149
lines changed

3 files changed

+167
-149
lines changed

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

+89-90
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,34 @@ set of source lines; instead, it will always line up with one of these blocks. T
1313
main types of scopes in Julia, *global scope* and *local scope*, the latter can be nested. The
1414
constructs introducing scope blocks are:
1515

16-
| Scope name | block/construct introducing this kind of scope |
17-
|:-------------------- |:-------------------------------------------------------------------------------------------------------- |
18-
| [Global Scope](@ref) | `module`, `baremodule`, at interactive prompt (REPL) |
19-
| [Local Scope](@ref) | [Soft Local Scope](@ref): `for`, `while`, comprehensions, try-catch-finally, `let` |
20-
| [Local Scope](@ref) | [Hard Local Scope](@ref): functions (either syntax, anonymous & do-blocks), `struct`, `macro` |
16+
# [](@id man-scope-table)
2117

22-
Notably missing from this table are [begin blocks](@ref man-compound-expressions) and [if blocks](@ref man-conditional-evaluation), which do *not*
23-
introduce new scope blocks. All three types of scopes follow somewhat different rules which will
24-
be explained below as well as some extra rules for certain blocks.
18+
* Scope blocks that may nest only in other global scope blocks:
19+
20+
- global scope
21+
22+
+ module, baremodule
23+
24+
+ at interactive prompt (REPL)
25+
26+
- local scope (don't allow nesting)
27+
28+
+ type, immutable, macro
29+
30+
* Scope blocks which may nest anywhere (in global or local scope):
31+
32+
- local scope
33+
34+
+ for, while, try-catch-finally, let
35+
36+
+ functions (either syntax, anonymous & do-blocks)
37+
38+
+ comprehensions, broadcast-fusing
39+
40+
Notably missing from this table are
41+
[begin blocks](@ref man-compound-expressions) and [if blocks](@ref man-conditional-evaluation)
42+
which do *not* introduce new scope blocks.
43+
Both types of scopes follow somewhat different rules which will be explained below.
2544

2645
Julia uses [lexical scoping](https://en.wikipedia.org/wiki/Scope_%28computer_science%29#Lexical_scoping_vs._dynamic_scoping),
2746
meaning that a function's scope does not inherit from its caller's scope, but from the scope in
@@ -50,7 +69,7 @@ Thus *lexical scope* means that the scope of variables can be inferred from the
5069

5170
## Global Scope
5271

53-
*Each module introduces a new global scope*, separate from the global scope of all other modules;
72+
Each module introduces a new global scope, separate from the global scope of all other modules;
5473
there is no all-encompassing global scope. Modules can introduce variables of other modules into
5574
their scope through the [using or import](@ref modules) statements or through qualified access using the
5675
dot-notation, i.e. each module is a so-called *namespace*. Note that variable bindings can only
@@ -87,16 +106,20 @@ Note that the interactive prompt (aka REPL) is in the global scope of the module
87106

88107
## Local Scope
89108

90-
A new local scope is introduced by most code-blocks, see above table for a complete list.
91-
A local scope *usually* inherits all the variables from its parent scope, both for reading and
92-
writing. There are two subtypes of local scopes, hard and soft, with slightly different rules
93-
concerning what variables are inherited. Unlike global scopes, local scopes are not namespaces,
109+
A new local scope is introduced by most code blocks (see above
110+
[table](@ref man-scope-table) for a complete list).
111+
A local scope inherits all the variables from a parent local scope,
112+
both for reading and writing.
113+
Additionally, the local scope inherits all globals that are assigned
114+
to in its parent global scope block (if it is surrounded by a global `if` or `begin` scope).
115+
Unlike global scopes, local scopes are not namespaces,
94116
thus variables in an inner scope cannot be retrieved from the parent scope through some sort of
95117
qualified access.
96118

97-
The following rules and examples pertain to both hard and soft local scopes. A newly introduced
98-
variable in a local scope does not back-propagate to its parent scope. For example, here the
99-
`z` is not introduced into the top-level scope:
119+
The following rules and examples pertain to local scopes.
120+
A newly introduced variable in a local scope does not
121+
back-propagate to its parent scope.
122+
For example, here the ``z`` is not introduced into the top-level scope:
100123

101124
```jldoctest
102125
julia> for i = 1:10
@@ -110,21 +133,21 @@ ERROR: UndefVarError: z not defined
110133
(Note, in this and all following examples it is assumed that their top-level is a global scope
111134
with a clean workspace, for instance a newly started REPL.)
112135

113-
Inside a local scope a variable can be forced to be a local variable using the `local` keyword:
136+
Inside a local scope a variable can be forced to be a new local variable using the `local` keyword:
114137

115138
```jldoctest
116139
julia> x = 0;
117140
118141
julia> for i = 1:10
119-
local x
142+
local x # this is also the default
120143
x = i + 1
121144
end
122145
123146
julia> x
124147
0
125148
```
126149

127-
Inside a local scope a new global variable can be defined using the keyword `global`:
150+
Inside a local scope a global variable can be assigned to by using the keyword `global`:
128151

129152
```jldoctest
130153
julia> for i = 1:10
@@ -152,37 +175,14 @@ julia> z
152175
The `local` and `global` keywords can also be applied to destructuring assignments, e.g.
153176
`local x, y = 1, 2`. In this case the keyword affects all listed variables.
154177

155-
### Soft Local Scope
156-
157-
> In a soft local scope, all variables are inherited from its parent scope unless a variable is
158-
> specifically marked with the keyword `local`.
159-
160-
Soft local scopes are introduced by for-loops, while-loops, comprehensions, try-catch-finally-blocks,
161-
and let-blocks. There are some extra rules for [Let Blocks](@ref) and for [For Loops and Comprehensions](@ref).
162-
163-
In the following example the `x` and `y` refer always to the same variables as the soft local
164-
scope inherits both read and write variables:
165-
166-
```jldoctest
167-
julia> x, y = 0, 1;
168-
169-
julia> for i = 1:10
170-
x = i + y + 1
171-
end
172-
173-
julia> x
174-
12
175-
```
176-
177-
### Hard Local Scope
178+
Local scopes are introduced by most block keywords,
179+
with notable exceptions of `begin` and `if`.
178180

179-
Hard local scopes are introduced by function definitions (in all their forms), struct type definition blocks,
180-
and macro-definitions.
181+
In a local scope, all variables are inherited from its parent
182+
global scope block unless:
181183

182-
> In a hard local scope, all variables are inherited from its parent scope unless:
183-
>
184-
> * an assignment would result in a modified *global* variable, or
185-
> * a variable is specifically marked with the keyword `local`.
184+
* an assignment would result in a modified *global* variable, or
185+
* a variable is specifically marked with the keyword `local`.
186186

187187
Thus global variables are only inherited for reading but not for writing:
188188

@@ -203,6 +203,14 @@ julia> x
203203

204204
An explicit `global` is needed to assign to a global variable:
205205

206+
!!! sidebar "Avoiding globals"
207+
Avoiding changing the value of global variables is considered by many
208+
to be a programming best-practice.
209+
One reason for this is that remotely changing the state of global variables in other
210+
modules should be done with care as it makes the local behavior of the program hard to reason about.
211+
This is why the scope blocks that introduce local scope require the ``global``
212+
keyword to declare the intent to modify a global variable.
213+
206214
```jldoctest
207215
julia> x = 1;
208216
@@ -216,8 +224,7 @@ julia> x
216224
2
217225
```
218226

219-
Note that *nested functions* can behave differently to functions defined in the global scope as
220-
they can modify their parent scope's *local* variables:
227+
Note that *nested functions* can modify their parent scope's *local* variables:
221228

222229
```jldoctest
223230
julia> x, y = 1, 2;
@@ -234,19 +241,40 @@ julia> function baz()
234241
julia> baz()
235242
22
236243
237-
julia> x, y
244+
julia> x, y # verify that global x and y are unchanged
238245
(1, 2)
239246
```
240247

241-
The distinction between inheriting global and local variables for assignment can lead to some
242-
slight differences between functions defined in local vs. global scopes. Consider the modification
243-
of the last example by moving `bar` to the global scope:
248+
The reason to allow *modifying local* variables of parent scopes in
249+
nested functions is to allow constructing `closures
250+
<https://en.wikipedia.org/wiki/Closure_%28computer_programming%29>`_
251+
which have a private state, for instance the ``state`` variable in the
252+
following example:
253+
254+
```jldoctest
255+
julia> let state = 0
256+
global counter() = (state += 1)
257+
end;
258+
259+
julia> counter()
260+
1
261+
262+
julia> counter()
263+
2
264+
```
265+
266+
See also the closures in the examples in the next two sections.
267+
268+
The distinction between inheriting global scope and nesting local scope
269+
can lead to some slight differences between functions
270+
defined in local vs. global scopes for variable assignments.
271+
Consider the modification of the last example by moving `bar` to the global scope:
244272

245273
```jldoctest
246274
julia> x, y = 1, 2;
247275
248276
julia> function bar()
249-
x = 10 # local
277+
x = 10 # local, no longer a closure variable
250278
return x + y
251279
end;
252280
@@ -258,11 +286,11 @@ julia> function quz()
258286
julia> quz()
259287
14
260288
261-
julia> x, y
289+
julia> x, y # verify that global x and y are unchanged
262290
(1, 2)
263291
```
264292

265-
Note that above subtlety does not pertain to type and macro definitions as they can only appear
293+
Note that the above nesting rules do not pertain to type and macro definitions as they can only appear
266294
at the global scope. There are special scoping rules concerning the evaluation of default and
267295
keyword function arguments which are described in the [Function section](@ref man-functions).
268296

@@ -293,9 +321,9 @@ they are actually called. As an example, here is an inefficient, mutually recurs
293321
if positive integers are even or odd:
294322

295323
```jldoctest
296-
julia> even(n) = n == 0 ? true : odd(n-1);
324+
julia> even(n) = (n == 0) ? true : odd(n - 1);
297325
298-
julia> odd(n) = n == 0 ? false : even(n-1);
326+
julia> odd(n) = (n == 0) ? false : even(n - 1);
299327
300328
julia> even(3)
301329
false
@@ -305,37 +333,8 @@ true
305333
```
306334

307335
Julia provides built-in, efficient functions to test for oddness and evenness called [`iseven`](@ref)
308-
and [`isodd`](@ref) so the above definitions should only be taken as examples.
309-
310-
### Hard vs. Soft Local Scope
311-
312-
Blocks which introduce a soft local scope, such as loops, are generally used to manipulate the
313-
variables in their parent scope. Thus their default is to fully access all variables in their
314-
parent scope.
315-
316-
Conversely, the code inside blocks which introduce a hard local scope (function, type, and macro
317-
definitions) can be executed at any place in a program. Remotely changing the state of global
318-
variables in other modules should be done with care and thus this is an opt-in feature requiring
319-
the `global` keyword.
320-
321-
The reason to allow *modifying local* variables of parent scopes in nested functions is to allow
322-
constructing [closures](https://en.wikipedia.org/wiki/Closure_%28computer_programming%29) which
323-
have a private state, for instance the `state` variable in the following example:
324-
325-
```jldoctest
326-
julia> let state = 0
327-
global counter
328-
counter() = state += 1
329-
end;
330-
331-
julia> counter()
332-
1
333-
334-
julia> counter()
335-
2
336-
```
337-
338-
See also the closures in the examples in the next two sections.
336+
and [`isodd`](@ref) so the above definitions should only be considered to be examples of scope,
337+
not efficient design.
339338

340339
### Let Blocks
341340

src/julia-syntax.scm

+22-16
Original file line numberDiff line numberDiff line change
@@ -1718,9 +1718,9 @@
17181718
(define (expand-for while lhs X body)
17191719
;; (for (= lhs X) body)
17201720
(let* ((coll (make-ssavalue))
1721-
(state (gensy))
1722-
(outer? (and (pair? lhs) (eq? (car lhs) 'outer)))
1723-
(lhs (if outer? (cadr lhs) lhs)))
1721+
(state (gensy))
1722+
(outer? (and (pair? lhs) (eq? (car lhs) 'outer)))
1723+
(lhs (if outer? (cadr lhs) lhs)))
17241724
`(block (= ,coll ,(expand-forms X))
17251725
(= ,state (call (top start) ,coll))
17261726
;; TODO avoid `local declared twice` error from this
@@ -2283,15 +2283,15 @@
22832283
'while
22842284
(lambda (e)
22852285
`(break-block loop-exit
2286-
(_while ,(expand-forms (cadr e))
2287-
(break-block loop-cont
2288-
(scope-block ,(blockify (expand-forms (caddr e))))))))
2286+
(_while ,(expand-forms (cadr e))
2287+
(break-block loop-cont
2288+
(scope-block ,(blockify (expand-forms (caddr e))))))))
22892289

22902290
'inner-while
22912291
(lambda (e)
22922292
`(_while ,(expand-forms (cadr e))
2293-
(break-block loop-cont
2294-
(scope-block ,(blockify (expand-forms (caddr e)))))))
2293+
(break-block loop-cont
2294+
(scope-block ,(blockify (expand-forms (caddr e)))))))
22952295

22962296
'break
22972297
(lambda (e)
@@ -2570,10 +2570,15 @@
25702570
(define (find-local-def-decls e) (find-decls 'local-def e))
25712571
(define (find-global-decls e) (find-decls 'global e))
25722572

2573-
(define (implicit-locals e env glob)
2573+
(define (implicit-locals e env deprecated-env glob)
25742574
;; const decls on non-globals introduce locals
25752575
(append! (diff (find-decls 'const e) glob)
2576-
(find-assigned-vars e env)))
2576+
(filter
2577+
(lambda (v)
2578+
(if (memq v deprecated-env)
2579+
(begin (syntax-deprecation #f (string "implicit assignment to global variable `" v "`") (string "global " v)) #f)
2580+
#t))
2581+
(find-assigned-vars e env))))
25772582

25782583
(define (unbound-vars e bound tab)
25792584
(cond ((or (eq? e 'true) (eq? e 'false) (eq? e UNUSED)) tab)
@@ -2600,9 +2605,9 @@
26002605
((eq? (car e) 'local-def) '(null)) ;; remove local decls
26012606
((eq? (car e) 'implicit-global) '(null)) ;; remove implicit-global decls
26022607
((eq? (car e) 'warn-if-existing)
2603-
(if (or (memq (cadr e) outerglobals) (memq (cadr e) implicitglobals))
2604-
`(warn-loop-var ,(cadr e))
2605-
'(null)))
2608+
(if (or (memq (cadr e) outerglobals) (memq (cadr e) implicitglobals))
2609+
`(warn-loop-var ,(cadr e))
2610+
'(null)))
26062611
((eq? (car e) 'lambda)
26072612
(let* ((lv (lam:vars e))
26082613
(env (append lv env))
@@ -2619,15 +2624,16 @@
26192624
((eq? (car e) 'scope-block)
26202625
(let* ((blok (cadr e)) ;; body of scope-block expression
26212626
(other-locals (if lam (caddr lam) '())) ;; locals that are explicitly part of containing lambda expression
2622-
(iglo (find-decls 'implicit-global blok)) ;; globals defined implicitly outside blok
2627+
(iglo (find-decls 'implicit-global blok)) ;; globals possibly defined implicitly outside blok
26232628
(glob (diff (find-global-decls blok) iglo)) ;; all globals declared in blok
26242629
(vars-def (check-dups (find-local-def-decls blok) '()))
26252630
(locals-declared (check-dups (find-local-decls blok) vars-def))
26262631
(locals-implicit (implicit-locals
26272632
blok
26282633
;; being declared global prevents a variable
26292634
;; assignment from introducing a local
2630-
(append env glob iglo outerglobals locals-declared vars-def)
2635+
(append env glob iglo locals-declared vars-def)
2636+
outerglobals
26312637
(append glob iglo)))
26322638
(vars (delete-duplicates (append! locals-declared locals-implicit)))
26332639
(all-vars (append vars vars-def))
@@ -2664,7 +2670,7 @@
26642670
(memq var implicitglobals) ;; remove anything only added implicitly in the last scope block
26652671
(memq var glob))))) ;; remove anything that's now global
26662672
renames)))
2667-
(new-oglo (append iglo outerglobals)) ;; list of all outer-globals from outside blok
2673+
(new-oglo (append iglo outerglobals)) ;; list of all implicit-globals from outside blok
26682674
(body (resolve-scopes- blok new-env new-oglo new-iglo lam new-renames #f))
26692675
(real-new-vars (append (diff vars need-rename) renamed))
26702676
(real-new-vars-def (append (diff vars-def need-rename-def) renamed-def)))

0 commit comments

Comments
 (0)