Skip to content

Commit 7784ca4

Browse files
committed
bring back v0.6 scope rules in the REPL
fixes #28789
1 parent 8b4232b commit 7784ca4

File tree

6 files changed

+150
-5
lines changed

6 files changed

+150
-5
lines changed

NEWS.md

+7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ Language changes
2424
* The syntax `(;)`, which used to parse as an empty block expression, is deprecated.
2525
In the future it will indicate an empty named tuple ([#30115]).
2626

27+
* The interactive REPL now uses "soft scope" for top-level expressions: an assignment inside a
28+
scope block such as a `for` loop automatically assigns to a global variable if one has been
29+
defined already. This matches the behavior of Julia versions 0.6 and prior, as well as
30+
[IJulia](https://github.com/JuliaLang/IJulia.jl).
31+
Note that this only affects expressions interactively typed or pasted directly into the
32+
default REPL.
33+
2734
Multi-threading changes
2835
-----------------------
2936

src/ast.c

+13
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ static jl_value_t *scm_to_julia(fl_context_t *fl_ctx, value_t e, jl_module_t *mo
131131
static value_t julia_to_scm(fl_context_t *fl_ctx, jl_value_t *v);
132132
static jl_value_t *jl_expand_macros(jl_value_t *expr, jl_module_t *inmodule, struct macroctx_stack *macroctx, int onelevel);
133133

134+
value_t fl_defined_julia_global(fl_context_t *fl_ctx, value_t *args, uint32_t nargs)
135+
{
136+
// tells whether a var is defined in and *by* the current module
137+
argcount(fl_ctx, "defined-julia-global", nargs, 1);
138+
(void)tosymbol(fl_ctx, args[0], "defined-julia-global");
139+
jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx);
140+
jl_sym_t *var = jl_symbol(symbol_name(fl_ctx, args[0]));
141+
jl_binding_t *b =
142+
(jl_binding_t*)ptrhash_get(&ctx->module->bindings, var);
143+
return (b != HT_NOTFOUND && b->owner == ctx->module) ? fl_ctx->T : fl_ctx->F;
144+
}
145+
134146
value_t fl_current_module_counter(fl_context_t *fl_ctx, value_t *args, uint32_t nargs)
135147
{
136148
jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx);
@@ -197,6 +209,7 @@ value_t fl_julia_logmsg(fl_context_t *fl_ctx, value_t *args, uint32_t nargs)
197209
}
198210

199211
static const builtinspec_t julia_flisp_ast_ext[] = {
212+
{ "defined-julia-global", fl_defined_julia_global },
200213
{ "current-julia-module-counter", fl_current_module_counter },
201214
{ "julia-scalar?", fl_julia_scalar },
202215
{ "julia-logmsg", fl_julia_logmsg },

src/jlfrontend.scm

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
'(error "malformed expression"))))
3030
thk))
3131

32+
;; this is overwritten when we run in actual julia
33+
(define (defined-julia-global v) #f)
34+
3235
;; parser entry points
3336

3437
;; parse one expression (if greedy) or atom, returning end position

src/julia-syntax.scm

+36-5
Original file line numberDiff line numberDiff line change
@@ -2430,19 +2430,29 @@
24302430
(define (find-local-def-decls e) (find-decls 'local-def e))
24312431
(define (find-global-decls e) (find-decls 'global e))
24322432

2433+
(define (find-softscope e)
2434+
(expr-contains-p
2435+
(lambda (x) (and (pair? x) (eq? (car x) 'softscope) x))
2436+
e
2437+
(lambda (x) (not (and (pair? x)
2438+
(memq (car x) '(lambda scope-block module toplevel)))))))
2439+
24332440
(define (check-valid-name e)
24342441
(or (valid-name? e)
24352442
(error (string "invalid identifier name \"" e "\""))))
24362443

2437-
(define (make-scope (lam #f) (args '()) (locals '()) (globals '()) (sp '()) (renames '()) (prev #f))
2438-
(vector lam args locals globals sp renames prev))
2444+
(define (make-scope (lam #f) (args '()) (locals '()) (globals '()) (sp '()) (renames '()) (prev #f) (soft? #f)
2445+
(implicit-globals '()))
2446+
(vector lam args locals globals sp renames prev soft? implicit-globals))
24392447
(define (scope:lam s) (aref s 0))
24402448
(define (scope:args s) (aref s 1))
24412449
(define (scope:locals s) (aref s 2))
24422450
(define (scope:globals s) (aref s 3))
24432451
(define (scope:sp s) (aref s 4))
24442452
(define (scope:renames s) (aref s 5))
24452453
(define (scope:prev s) (aref s 6))
2454+
(define (scope:soft? s) (aref s 7))
2455+
(define (scope:implicit-globals s) (aref s 8))
24462456

24472457
(define (var-kind var scope)
24482458
(if scope
@@ -2492,6 +2502,8 @@
24922502
(if (not (in-scope? (cadr e) scope))
24932503
(error "no outer local variable declaration exists for \"for outer\""))
24942504
'(null))
2505+
((eq? (car e) 'softscope)
2506+
'(null))
24952507
((eq? (car e) 'locals)
24962508
(let* ((names (filter (lambda (v)
24972509
(and (not (gensym? v))
@@ -2520,20 +2532,35 @@
25202532
(let* ((blok (cadr e)) ;; body of scope-block expression
25212533
(lam (scope:lam scope))
25222534
(argnames (lam:vars lam))
2535+
(toplevel? (and (null? argnames) (eq? e (lam:body lam))))
25232536
(current-locals (caddr lam)) ;; locals created so far in our lambda
25242537
(globals (find-global-decls blok))
2538+
(assigned (find-assigned-vars blok))
25252539
(locals-def (find-local-def-decls blok))
25262540
(local-decls (find-local-decls blok))
2527-
(toplevel? (and (null? argnames) (eq? e (lam:body lam))))
2541+
(soft? (and (null? argnames)
2542+
(let ((ss (find-softscope blok)))
2543+
(cond ((not ss) (scope:soft? scope))
2544+
((equal? (cadr ss) '(true)) #t)
2545+
((equal? (cadr ss) '(false)) #f)
2546+
(else (scope:soft? scope))))))
2547+
(implicit-globals (if (and toplevel? soft?)
2548+
(filter (lambda (v) (and (not (memq v locals-def))
2549+
(not (memq v local-decls))))
2550+
assigned)
2551+
'()))
25282552
(implicit-locals
25292553
(filter (if toplevel?
25302554
;; make only assigned gensyms implicitly local at top level
25312555
some-gensym?
25322556
(lambda (v) (and (memq (var-kind v scope) '(none static-parameter))
2557+
(not (and soft?
2558+
(or (memq v (scope:implicit-globals scope))
2559+
(defined-julia-global v))))
25332560
(not (memq v locals-def))
25342561
(not (memq v local-decls))
25352562
(not (memq v globals)))))
2536-
(find-assigned-vars blok)))
2563+
assigned))
25372564
(locals-nondef (delete-duplicates (append local-decls implicit-locals)))
25382565
(need-rename? (lambda (vars)
25392566
(filter (lambda (v) (or (memq v current-locals) (in-scope? v scope)))
@@ -2574,7 +2601,11 @@
25742601
'()
25752602
(append (map cons need-rename renamed)
25762603
(map cons need-rename-def renamed-def))
2577-
scope)))
2604+
scope
2605+
(and soft? (null? argnames))
2606+
(if toplevel?
2607+
implicit-globals
2608+
(scope:implicit-globals scope)))))
25782609
(append! (map (lambda (v) `(local ,v)) newnames)
25792610
(map (lambda (v) `(local-def ,v)) newnames-def)))
25802611
))

stdlib/REPL/src/REPL.jl

+21
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,24 @@ mutable struct REPLBackend
7373
new(repl_channel, response_channel, in_eval)
7474
end
7575

76+
function softscope!(ex)
77+
if ex isa Expr
78+
h = ex.head
79+
if h === :toplevel
80+
for i = 1:length(ex.args)
81+
ex.args[i] = softscope!(ex.args[i])
82+
end
83+
elseif h in (:meta, :import, :using, :export, :module, :error, :thunk)
84+
return ex
85+
else
86+
return Expr(:block, Expr(:softscope, true), ex)
87+
end
88+
end
89+
return ex
90+
end
91+
92+
const repl_ast_transforms = Any[softscope!]
93+
7694
function eval_user_input(@nospecialize(ast), backend::REPLBackend)
7795
lasterr = nothing
7896
Base.sigatomic_begin()
@@ -83,6 +101,9 @@ function eval_user_input(@nospecialize(ast), backend::REPLBackend)
83101
put!(backend.response_channel, (lasterr,true))
84102
else
85103
backend.in_eval = true
104+
for xf in repl_ast_transforms
105+
ast = xf(ast)
106+
end
86107
value = Core.eval(Main, ast)
87108
backend.in_eval = false
88109
# note: use jl_set_global to make sure value isn't passed through `expand`

test/syntax.jl

+70
Original file line numberDiff line numberDiff line change
@@ -1971,3 +1971,73 @@ end
19711971
# issue #33987
19721972
f33987(args::(Vararg{Any, N} where N); kwargs...) = args
19731973
@test f33987(1,2,3) === (1,2,3)
1974+
1975+
# optional soft scope: #28789, #33864
1976+
1977+
@test @eval begin
1978+
$(Expr(:softscope, true))
1979+
x28789 = 0 # new global included in same expression
1980+
for i = 1:2
1981+
x28789 += i
1982+
end
1983+
x28789
1984+
end == 3
1985+
1986+
y28789 = 1 # new global defined in separate top-level input
1987+
@eval begin
1988+
$(Expr(:softscope, true))
1989+
for i = 1:10
1990+
y28789 += i
1991+
end
1992+
end
1993+
@test y28789 == 56
1994+
1995+
@eval begin
1996+
$(Expr(:softscope, true))
1997+
for i = 10:10
1998+
z28789 = i
1999+
end
2000+
@test z28789 == 10
2001+
z28789 = 0 # new global assigned after loop but in same soft scope
2002+
end
2003+
2004+
@eval begin
2005+
$(Expr(:softscope, true))
2006+
let y28789 = 0 # shadowing with let
2007+
y28789 = 1
2008+
end
2009+
end
2010+
@test y28789 == 56
2011+
2012+
@eval begin
2013+
$(Expr(:softscope, true))
2014+
let y28789 = 0
2015+
let x = 2
2016+
let y = 3
2017+
z28789 = 42 # assign to global despite several lets
2018+
end
2019+
end
2020+
end
2021+
end
2022+
@test z28789 == 42
2023+
2024+
@eval begin
2025+
$(Expr(:softscope, true))
2026+
let x = 0
2027+
ww28789 = 88 # not global
2028+
let y = 3
2029+
ww28789 = 89
2030+
end
2031+
@test ww28789 == 89
2032+
end
2033+
end
2034+
@test !@isdefined(ww28789)
2035+
2036+
@eval begin
2037+
$(Expr(:softscope, true))
2038+
function f28789()
2039+
z28789 = 43
2040+
end
2041+
f28789()
2042+
end
2043+
@test z28789 == 42

0 commit comments

Comments
 (0)