Skip to content

Commit e89bb75

Browse files
committed
remove implicitly inherited globals
fix #19324
1 parent 3b94a9e commit e89bb75

File tree

12 files changed

+212
-188
lines changed

12 files changed

+212
-188
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

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

294322
```jldoctest
295-
julia> even(n) = n == 0 ? true : odd(n-1);
323+
julia> even(n) = (n == 0) ? true : odd(n - 1);
296324
297-
julia> odd(n) = n == 0 ? false : even(n-1);
325+
julia> odd(n) = (n == 0) ? false : even(n - 1);
298326
299327
julia> even(3)
300328
false
@@ -304,37 +332,8 @@ true
304332
```
305333

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

339338
### Let Blocks
340339

src/julia-syntax.scm

+19-13
Original file line numberDiff line numberDiff line change
@@ -1710,9 +1710,9 @@
17101710
(define (expand-for while lhs X body)
17111711
;; (for (= lhs X) body)
17121712
(let* ((coll (make-ssavalue))
1713-
(state (gensy))
1714-
(outer? (and (pair? lhs) (eq? (car lhs) 'outer)))
1715-
(lhs (if outer? (cadr lhs) lhs)))
1713+
(state (gensy))
1714+
(outer? (and (pair? lhs) (eq? (car lhs) 'outer)))
1715+
(lhs (if outer? (cadr lhs) lhs)))
17161716
`(block (= ,coll ,(expand-forms X))
17171717
(= ,state (call (top start) ,coll))
17181718
;; TODO avoid `local declared twice` error from this
@@ -2275,15 +2275,15 @@
22752275
'while
22762276
(lambda (e)
22772277
`(break-block loop-exit
2278-
(_while ,(expand-forms (cadr e))
2279-
(break-block loop-cont
2280-
(scope-block ,(blockify (expand-forms (caddr e))))))))
2278+
(_while ,(expand-forms (cadr e))
2279+
(break-block loop-cont
2280+
(scope-block ,(blockify (expand-forms (caddr e))))))))
22812281

22822282
'inner-while
22832283
(lambda (e)
22842284
`(_while ,(expand-forms (cadr e))
2285-
(break-block loop-cont
2286-
(scope-block ,(blockify (expand-forms (caddr e)))))))
2285+
(break-block loop-cont
2286+
(scope-block ,(blockify (expand-forms (caddr e)))))))
22872287

22882288
'break
22892289
(lambda (e)
@@ -2560,10 +2560,15 @@
25602560
(define (find-local-def-decls e) (find-decls 'local-def e))
25612561
(define (find-global-decls e) (find-decls 'global e))
25622562

2563-
(define (implicit-locals e env glob)
2563+
(define (implicit-locals e env deprecated-env glob)
25642564
;; const decls on non-globals introduce locals
25652565
(append! (diff (find-decls 'const e) glob)
2566-
(find-assigned-vars e env)))
2566+
(filter
2567+
(lambda (v)
2568+
(if (memq v deprecated-env)
2569+
(begin (syntax-deprecation #f (string "implicit assignment to global variable `" v "`") (string "global " v)) #f)
2570+
#t))
2571+
(find-assigned-vars e env))))
25672572

25682573
(define (unbound-vars e bound tab)
25692574
(cond ((or (eq? e 'true) (eq? e 'false) (eq? e UNUSED)) tab)
@@ -2613,15 +2618,16 @@
26132618
((eq? (car e) 'scope-block)
26142619
(let* ((blok (cadr e)) ;; body of scope-block expression
26152620
(other-locals (if lam (caddr lam) '())) ;; locals that are explicitly part of containing lambda expression
2616-
(iglo (find-decls 'implicit-global blok)) ;; globals defined implicitly outside blok
2621+
(iglo (find-decls 'implicit-global blok)) ;; globals possibly defined implicitly outside blok
26172622
(glob (diff (find-global-decls blok) iglo)) ;; all globals declared in blok
26182623
(vars-def (check-dups (find-local-def-decls blok) '()))
26192624
(locals-declared (check-dups (find-local-decls blok) vars-def))
26202625
(locals-implicit (implicit-locals
26212626
blok
26222627
;; being declared global prevents a variable
26232628
;; assignment from introducing a local
2624-
(append env glob iglo outerglobals locals-declared vars-def)
2629+
(append env glob iglo locals-declared vars-def)
2630+
outerglobals
26252631
(append glob iglo)))
26262632
(vars (delete-duplicates (append! locals-declared locals-implicit)))
26272633
(all-vars (append vars vars-def))
@@ -2658,7 +2664,7 @@
26582664
(memq var implicitglobals) ;; remove anything only added implicitly in the last scope block
26592665
(memq var glob))))) ;; remove anything that's now global
26602666
renames)))
2661-
(new-oglo (append iglo outerglobals)) ;; list of all outer-globals from outside blok
2667+
(new-oglo (append iglo outerglobals)) ;; list of all implicit-globals from outside blok
26622668
(body (resolve-scopes- blok new-env new-oglo new-iglo lam new-renames #f))
26632669
(real-new-vars (append (diff vars need-rename) renamed))
26642670
(real-new-vars-def (append (diff vars-def need-rename-def) renamed-def)))

0 commit comments

Comments
 (0)