Skip to content

Commit a4f7d00

Browse files
committed
const lowering: respect scope, prohibit non-const const assignment
Modifies the handling of the 3-argument "lowered const" so `const x = y` participates in scope resolution again (#56613); we treat (const x y) as an assignment form, producing the "unsupported `const` declaration on local variable" error if the usual scope rules would not make x global. At the top level, these should succeed: const x = 1 begin const x = 1 end let const global x = 1 end Each of the following should fail: # ERROR: syntax: unsupported `const` declaration on local variable around REPL[1]:2 let const x = 1 end # ERROR: syntax: unsupported `const` declaration on local variable around REPL[1]:2 function f() const x = 1 end This also amkes the use of `const global` inside a function body produce the original, specific error message rather than a generic "World age increment not at top level" error (#57334): # ERROR: syntax: `global const` declaration not allowed inside function around REPL[1]:2 function f() global const x = 1 end Issue #57334 revealed we silently accept `const` expressions writing to variables. These now fail with a new error message: a = [1, 2] # ERROR: syntax: cannot declare "a[1]" `const` around REPL[1]:2 const a[1] = 3 The bulk of these changes track const-ness through the functions called by expand-assignment, since each of the three types of LHS is affect differently: - ordinary variables become `const`, producing error if not global - `X{T} = ...` forces X to be `const`, rather than the defaulting to being implicitly const only if in global scope. - `f() = ...` ignores `const`.
1 parent 00ecd8b commit a4f7d00

File tree

1 file changed

+96
-75
lines changed

1 file changed

+96
-75
lines changed

src/julia-syntax.scm

Lines changed: 96 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,7 +1423,7 @@
14231423
(else
14241424
(error "invalid \"try\" form")))))
14251425

1426-
(define (expand-unionall-def name type-ex (allow-local #t))
1426+
(define (expand-unionall-def name type-ex (const? #t))
14271427
(if (and (pair? name)
14281428
(eq? (car name) 'curly))
14291429
(let ((name (cadr name))
@@ -1434,7 +1434,7 @@
14341434
(expand-forms
14351435
`(block
14361436
(= ,rr (where ,type-ex ,@params))
1437-
(,(if allow-local 'assign-const-if-global 'const) ,name ,rr)
1437+
(,(if const? 'const 'assign-const-if-global) ,name ,rr)
14381438
(latestworld-if-toplevel)
14391439
,rr)))
14401440
(expand-forms
@@ -1444,38 +1444,24 @@
14441444
(filter (lambda (x) (not (underscore-symbol? x))) syms))
14451445

14461446
;; Expand `[global] const a::T = val`
1447-
(define (expand-const-decl e (mustassgn #f))
1448-
(if (length= e 3) e
1449-
(let ((arg (cadr e)))
1450-
(if (atom? arg)
1451-
(if mustassgn
1452-
(error "expected assignment after \"const\"")
1453-
e)
1454-
(case (car arg)
1455-
((global)
1456-
(expand-const-decl `(const ,(cadr arg)) #t))
1457-
((=)
1458-
(cond
1459-
;; `const f() = ...` - The `const` here is inoperative, but the syntax happened to work in earlier versions, so simply strip `const`.
1460-
;; TODO: Consider whether to keep this in 2.0.
1461-
((eventually-call? (cadr arg))
1462-
(expand-forms arg))
1463-
((and (pair? (cadr arg)) (eq? (caadr arg) 'curly))
1464-
(expand-unionall-def (cadr arg) (caddr arg)))
1465-
((and (pair? (cadr arg)) (eq? (caadr arg) 'tuple) (not (has-parameters? (cdr (cadr arg)))))
1466-
;; We need this case because `(f(), g()) = (1, 2)` goes through here, which cannot go via the `local` lowering below,
1467-
;; because the symbols come out wrong. Sigh... So much effort for such a syntax corner case.
1468-
(expand-tuple-destruct (cdr (cadr arg)) (caddr arg) (lambda (assgn) `(,(car e) ,assgn))))
1469-
(else
1470-
(let ((rr (make-ssavalue)))
1471-
(expand-forms `(block
1472-
(= ,rr ,(caddr arg))
1473-
(scope-block (block (hardscope)
1474-
(local (= ,(cadr arg) ,rr))
1475-
,.(map (lambda (v) `(,(car e) (globalref (thismodule) ,v) ,v)) (filter-not-underscore (lhs-vars (cadr arg))))
1476-
(latestworld)
1477-
,rr))))))))
1478-
(else (error "expected assignment after \"const\"")))))))
1447+
(define (expand-const-decl e)
1448+
(define (check-assignment asgn)
1449+
(unless (and (pair? asgn) (eq? (car asgn) '=))
1450+
;; (const (global x)) is possible due to a parser quirk
1451+
(error "expected assignment after \"const\"")))
1452+
(if (length= e 3)
1453+
`(const ,(cadr e) ,(expand-forms (caddr e)))
1454+
(let ((arg (cadr e)))
1455+
(case (car arg)
1456+
((global) (let ((asgn (cadr arg)))
1457+
(check-assignment asgn)
1458+
`(block
1459+
,.(map (lambda (v) `(global ,v))
1460+
(filter-not-underscore (lhs-vars (cadr asgn))))
1461+
,(expand-assignment asgn #t))))
1462+
((=) (check-assignment arg)
1463+
(expand-assignment arg #t))
1464+
(else (error "expected assignment after \"const\""))))))
14791465

14801466
(define (expand-atomic-decl e)
14811467
(error "unimplemented or unsupported atomic declaration"))
@@ -1532,35 +1518,54 @@
15321518
(eq? (car (cadr lhs)) 'call)))))
15331519
(define (assignment-to-function lhs e) ;; convert '= expr to 'function expr
15341520
(cons 'function (cdr e)))
1521+
(define (maybe-wrap-const x)
1522+
(if const? `(const ,x) x))
15351523
(cond
15361524
((function-lhs? lhs)
1525+
;; `const f() = ...` - The `const` here is inoperative, but the syntax
1526+
;; happened to work in earlier versions, so simply strip `const`.
15371527
(expand-forms (assignment-to-function lhs e)))
15381528
((and (pair? lhs)
15391529
(eq? (car lhs) 'curly))
1540-
(expand-unionall-def (cadr e) (caddr e)))
1530+
(expand-unionall-def (cadr e) (caddr e) const?))
15411531
((assignment? (caddr e))
15421532
;; chain of assignments - convert a=b=c to `b=c; a=c`
15431533
(let loop ((lhss (list lhs))
15441534
(rhs (caddr e)))
15451535
(if (and (assignment? rhs) (not (function-lhs? (cadr rhs))))
15461536
(loop (cons (cadr rhs) lhss) (caddr rhs))
1547-
(let ((rr (if (symbol-like? rhs) rhs (make-ssavalue))))
1537+
(let ((rr (if (symbol-like? rhs) rhs (make-ssavalue)))
1538+
(lhss (reverse lhss)))
15481539
(expand-forms
15491540
`(block ,.(if (eq? rr rhs) '() `((= ,rr ,(if (assignment? rhs)
15501541
(assignment-to-function (cadr rhs) rhs)
15511542
rhs))))
1543+
;; In const x = y = z, only x becomes const
1544+
,(maybe-wrap-const `(= ,(car lhss) ,rr))
15521545
,@(map (lambda (l) `(= ,l ,rr))
1553-
lhss)
1546+
(cdr lhss))
15541547
(unnecessary ,rr)))))))
15551548
((or (and (symbol-like? lhs) (valid-name? lhs))
15561549
(globalref? lhs))
1557-
(sink-assignment lhs (expand-forms (caddr e))))
1550+
;; TODO: We currently call (latestworld) after every (const _ _), but this
1551+
;; may need to be moved elsewhere if we want to avoid making one const
1552+
;; visible before side effects have been performed (#57484)
1553+
(if const?
1554+
(let ((rr (make-ssavalue)))
1555+
`(block
1556+
,(sink-assignment rr (expand-forms (caddr e)))
1557+
(const ,lhs ,rr)
1558+
(latestworld)
1559+
(unnecessary ,rr)))
1560+
(sink-assignment lhs (expand-forms (caddr e)))))
15581561
((atom? lhs)
15591562
(error (string "invalid assignment location \"" (deparse lhs) "\"")))
15601563
(else
15611564
(case (car lhs)
15621565
((|.|)
15631566
;; a.b =
1567+
(when const?
1568+
(error (string "cannot declare \"" (deparse lhs) "\" `const`")))
15641569
(let* ((a (cadr lhs))
15651570
(b (caddr lhs))
15661571
(rhs (caddr e)))
@@ -1582,15 +1587,17 @@
15821587
(x (caddr e)))
15831588
(if (has-parameters? lhss)
15841589
;; property destructuring
1585-
(expand-property-destruct lhss x)
1590+
(expand-property-destruct lhss x maybe-wrap-const)
15861591
;; multiple assignment
1587-
(expand-tuple-destruct lhss x))))
1592+
(expand-tuple-destruct lhss x maybe-wrap-const))))
15881593
((typed_hcat)
15891594
(error "invalid spacing in left side of indexed assignment"))
15901595
((typed_vcat typed_ncat)
15911596
(error "unexpected \";\" in left side of indexed assignment"))
15921597
((ref)
15931598
;; (= (ref a . idxs) rhs)
1599+
(when const?
1600+
(error (string "cannot declare \"" (deparse lhs) "\" `const`")))
15941601
(let ((a (cadr lhs))
15951602
(idxs (cddr lhs))
15961603
(rhs (caddr e)))
@@ -1620,10 +1627,24 @@
16201627
(T (caddr lhs))
16211628
(rhs (caddr e)))
16221629
(let ((e (remove-argument-side-effects x)))
1623-
(expand-forms
1624-
`(block ,@(cdr e)
1625-
(decl ,(car e) ,T)
1626-
(= ,(car e) ,rhs))))))
1630+
(if const?
1631+
;; This could go through convert-assignment in the closure
1632+
;; conversion pass, but since constants don't have declared types
1633+
;; the way other variables do, we insert convert() here.
1634+
(expand-forms
1635+
;; TODO: This behaviour (`const _:T = ...` does not call convert,
1636+
;; but still evaluates RHS) should be documented.
1637+
`(const ,(car e) ,(if (underscore-symbol? (car e))
1638+
rhs
1639+
(convert-for-type-decl rhs T #t #f))))
1640+
(expand-forms
1641+
`(block ,@(cdr e)
1642+
;; TODO: When x is a complex expression, this acts as a
1643+
;; typeassert rather than a declaration.
1644+
,.(if (underscore-symbol? (car e))
1645+
'() ; Assignment to _ will ultimately be discarded---don't declare anything
1646+
`((decl ,(car e) ,T)))
1647+
,(maybe-wrap-const `(= ,(car e) ,rhs))))))))
16271648
((vcat ncat)
16281649
;; (= (vcat . args) rhs)
16291650
(error "use \"(a, b) = ...\" to assign multiple values"))
@@ -2365,7 +2386,7 @@
23652386
(gensy))
23662387
(else (make-ssavalue))))
23672388

2368-
(define (expand-property-destruct lhs x)
2389+
(define (expand-property-destruct lhs x (wrap identity))
23692390
(if (not (length= lhs 1))
23702391
(error (string "invalid assignment location \"" (deparse `(tuple ,lhs)) "\"")))
23712392
(let* ((lhss (cdar lhs))
@@ -2380,7 +2401,7 @@
23802401
(cadr field))
23812402
(else
23822403
(error (string "invalid assignment location \"" (deparse `(tuple ,lhs)) "\""))))))
2383-
(expand-forms `(= ,field (call (top getproperty) ,xx (quote ,prop))))))
2404+
(expand-forms (wrap `(= ,field (call (top getproperty) ,xx (quote ,prop)))))))
23842405
lhss)
23852406
(unnecessary ,xx))))
23862407

@@ -2401,7 +2422,6 @@
24012422
(if (null? lhss)
24022423
'()
24032424
(let* ((lhs (car lhss))
2404-
(wrapfirst (lambda (x i) (if (= i 1) (wrap x) x)))
24052425
(lhs- (cond ((or (symbol? lhs) (ssavalue? lhs))
24062426
lhs)
24072427
((vararg? lhs)
@@ -2413,7 +2433,10 @@
24132433
(make-ssavalue))))))
24142434
;; can't use ssavalues if it's a function definition
24152435
((eventually-call? lhs) (gensy))
2416-
(else (make-ssavalue)))))
2436+
(else (make-ssavalue))))
2437+
;; If we use an intermediary lhs, don't wrap `const`.
2438+
(wrap-subassign (if (eq? lhs lhs-) wrap identity))
2439+
(wrapfirst (lambda (x i) (if (= i 1) (wrap-subassign x) x))))
24172440
(if (and (vararg? lhs) (any vararg? (cdr lhss)))
24182441
(error "multiple \"...\" on lhs of assignment"))
24192442
(if (not (eq? lhs lhs-))
@@ -2425,7 +2448,7 @@
24252448
(if (underscore-symbol? (cadr lhs-))
24262449
'()
24272450
(list (expand-forms
2428-
(wrap `(= ,(cadr lhs-) (call (top rest) ,xx ,@(if (eq? i 1) '() `(,st))))))))
2451+
(wrap-subassign `(= ,(cadr lhs-) (call (top rest) ,xx ,@(if (eq? i 1) '() `(,st))))))))
24292452
(let ((tail (if (eventually-call? lhs) (gensy) (make-ssavalue))))
24302453
(cons (expand-forms
24312454
(lower-tuple-assignment
@@ -2998,7 +3021,7 @@
29983021
;; like v = val, except that if `v` turns out global(either
29993022
;; implicitly or by explicit `global`), it gains an implicit `const`
30003023
(set! vars (cons (cadr e) vars)))
3001-
((=)
3024+
((= const)
30023025
(let ((v (decl-var (cadr e))))
30033026
(find-assigned-vars- (caddr e))
30043027
(if (or (ssavalue? v) (globalref? v) (underscore-symbol? v))
@@ -3127,7 +3150,7 @@
31273150
((eq? (car e) 'assign-const-if-global)
31283151
(if (eq? (var-kind (cadr e) scope) 'local)
31293152
(if (length= e 2) (null) `(= ,@(cdr e)))
3130-
`(const ,@(cdr e))))
3153+
(resolve-scopes- `(const ,@(cdr e)) scope sp loc)))
31313154
((memq (car e) '(local local-def))
31323155
(check-valid-name (cadr e))
31333156
;; remove local decls
@@ -3280,7 +3303,7 @@
32803303
,(resolve-scopes- (caddr e) scope)
32813304
,(resolve-scopes- (cadddr e) scope (method-expr-static-parameters e))))
32823305
(else
3283-
(if (and (eq? (car e) '=) (symbol? (cadr e))
3306+
(if (and (memq (car e) '(= const)) (symbol? (cadr e))
32843307
scope (null? (lam:args (scope:lam scope)))
32853308
(warn-var?! (cadr e) scope)
32863309
(= *scopewarn-opt* 1))
@@ -3400,7 +3423,7 @@
34003423
((local-def) ;; a local that we know has an assignment that dominates all usages
34013424
(let ((vi (get tab (cadr e) #f)))
34023425
(vinfo:set-never-undef! vi #t)))
3403-
((=)
3426+
((= const)
34043427
(let ((vi (and (symbol? (cadr e)) (get tab (cadr e) #f))))
34053428
(if vi ; if local or captured
34063429
(begin (if (vinfo:asgn vi)
@@ -4017,7 +4040,10 @@ f(x) = yt(x)
40174040
'(null)
40184041
`(newvar ,(cadr e))))))
40194042
((const)
4020-
(put! globals (binding-to-globalref (cadr e)) #f)
4043+
;; Check we've expanded surface `const` (1 argument form)
4044+
(assert (and (length= e 3)))
4045+
(when (globalref? (cadr e))
4046+
(put! globals (cadr e) #f))
40214047
e)
40224048
((atomic) e)
40234049
((isdefined) ;; convert isdefined expr to function for closure converted variables
@@ -4369,7 +4395,6 @@ f(x) = yt(x)
43694395
(first-line #t)
43704396
(current-loc #f)
43714397
(rett #f)
4372-
(global-const-error #f)
43734398
(vinfo-table (vinfo-to-table (car (lam:vinfo lam))))
43744399
(arg-map #f) ;; map arguments to new names if they are assigned
43754400
(label-counter 0) ;; counter for generating label addresses
@@ -4582,18 +4607,19 @@ f(x) = yt(x)
45824607
(cdr cnd)
45834608
(list cnd))))))
45844609
tests))
4585-
(define (emit-assignment-or-setglobal lhs rhs)
4586-
(if (globalref? lhs)
4610+
(define (emit-assignment-or-setglobal lhs rhs (op '=))
4611+
;; (const (globalref _ _) _) does not use setglobal!
4612+
(if (and (globalref? lhs) (eq? op '=))
45874613
(emit `(call (top setglobal!) ,(cadr lhs) (inert ,(caddr lhs)) ,rhs))
4588-
(emit `(= ,lhs ,rhs))))
4589-
(define (emit-assignment lhs rhs)
4614+
(emit `(,op ,lhs ,rhs))))
4615+
(define (emit-assignment lhs rhs (op '=))
45904616
(if rhs
45914617
(if (valid-ir-rvalue? lhs rhs)
4592-
(emit-assignment-or-setglobal lhs rhs)
4618+
(emit-assignment-or-setglobal lhs rhs op)
45934619
(let ((rr (make-ssavalue)))
45944620
(emit `(= ,rr ,rhs))
4595-
(emit-assignment-or-setglobal lhs rr)))
4596-
(emit-assignment-or-setglobal lhs `(null))) ; in unreachable code (such as after return), still emit the assignment so that the structure of those uses is preserved
4621+
(emit-assignment-or-setglobal lhs rr op)))
4622+
(emit-assignment-or-setglobal lhs `(null) op)) ; in unreachable code (such as after return), still emit the assignment so that the structure of those uses is preserved
45974623
#f)
45984624
;; the interpreter loop. `break-labels` keeps track of the labels to jump to
45994625
;; for all currently closing break-blocks.
@@ -4659,7 +4685,12 @@ f(x) = yt(x)
46594685
(cond (tail (emit-return tail callex))
46604686
(value callex)
46614687
(else (emit callex)))))
4662-
((=)
4688+
((= const)
4689+
(when (eq? (car e) 'const)
4690+
(when (local-in? (cadr e) lam)
4691+
(error (string "unsupported `const` declaration on local variable" (format-loc current-loc))))
4692+
(when (pair? (cadr lam))
4693+
(error (string "`global const` declaration not allowed inside function" (format-loc current-loc)))))
46634694
(let ((lhs (cadr e)))
46644695
(if (and (symbol? lhs) (underscore-symbol? lhs))
46654696
(compile (caddr e) break-labels value tail)
@@ -4672,10 +4703,10 @@ f(x) = yt(x)
46724703
rhs (make-ssavalue))))
46734704
(if (not (eq? rr rhs))
46744705
(emit `(= ,rr ,rhs)))
4675-
(emit-assignment-or-setglobal lhs rr)
4706+
(emit-assignment-or-setglobal lhs rr (car e))
46764707
(if tail (emit-return tail rr))
46774708
rr)
4678-
(emit-assignment lhs rhs))))))
4709+
(emit-assignment lhs rhs (car e)))))))
46794710
((block)
46804711
(let* ((last-fname filename)
46814712
(fnm (first-non-meta e))
@@ -4918,14 +4949,6 @@ f(x) = yt(x)
49184949
((moved-local)
49194950
(set-car! (lam:vinfo lam) (append (car (lam:vinfo lam)) `((,(cadr e) Any 2))))
49204951
#f)
4921-
((const)
4922-
(if (local-in? (cadr e) lam)
4923-
(error (string "unsupported `const` declaration on local variable" (format-loc current-loc)))
4924-
(if (pair? (cadr lam))
4925-
;; delay this error to allow "misplaced struct" errors to happen first
4926-
(if (not global-const-error)
4927-
(set! global-const-error current-loc))
4928-
(emit e))))
49294952
((atomic) (error "misplaced atomic declaration"))
49304953
((isdefined throw_undef_if_not) (if tail (emit-return tail e) e))
49314954
((boundscheck) (if tail (emit-return tail e) e))
@@ -5056,8 +5079,6 @@ f(x) = yt(x)
50565079
(let ((pexc (pop-exc-expr src-catch-tokens target-catch-tokens)))
50575080
(if pexc (set-cdr! point (cons pexc (cdr point)))))))))
50585081
handler-goto-fixups)
5059-
(if global-const-error
5060-
(error (string "`global const` declaration not allowed inside function" (format-loc global-const-error))))
50615082
(let* ((stmts (reverse! code))
50625083
(di (definitely-initialized-vars stmts vi))
50635084
(body (cons 'block (filter (lambda (e)

0 commit comments

Comments
 (0)