Skip to content

Commit b00020f

Browse files
simeonschaubAmit Shirodkar
authored and
Amit Shirodkar
committed
don't mutate lhs while destructuring (JuliaLang#40737)
Jeff was a bit sceptical about this in JuliaLang#40574, but I think this is worth doing since it seems strictly more useful than the current behavior and we already go out of our way in cases like `x, y = y, x` to only assign to the lhs after the rhs has been evaluated, so I think handling `x[2], x[1] = x` in a similar way would only be consistent. As a general rule that assignment will always happen after destructuring does not seem much less obvious than the current behavior to me. This might technically be slightly breaking, but I would be surprised if people relied on the current behavior here. Nevertheless, it would probably be good to run PkgEval if we decide to go with this change. closes JuliaLang#40574
1 parent d7a6e06 commit b00020f

File tree

3 files changed

+42
-10
lines changed

3 files changed

+42
-10
lines changed

NEWS.md

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ Language changes
2323
* `macroexpand`, `@macroexpand`, and `@macroexpand1` no longer wrap errors in a `LoadError`. To reduce breakage, `@test_throws` has been modified so that many affected tests will still pass ([#38379]].
2424
* The middle dot `·` (`\cdotp` U+00b7) and the Greek interpunct `·` (U+0387) are now treated as equivalent to the dot operator `` (`\cdot` U+22c5) (#25157).
2525
* The minus sign `` (`\minus` U+2212) is now treated as equivalent to the hyphen-minus sign `-` (U+002d).
26+
* Destructuring will no longer mutate values on the left hand side while iterating through values on the right hand side. In the example
27+
of an array `x`, `x[2], x[1] = x` will now swap the first and second entry of `x`, whereas it used to fill both entries with `x[1]`
28+
because `x[2]` was mutated during the iteration of `x`. ([#40737])
2629

2730
Compiler/Runtime improvements
2831
-----------------------------

src/julia-syntax.scm

+28-10
Original file line numberDiff line numberDiff line change
@@ -2143,22 +2143,40 @@
21432143
(- n 1)
21442144
n))
21452145
n))
2146-
(st (gensy)))
2146+
(st (gensy))
2147+
(end '()))
21472148
`(block
21482149
,@(if (> n 0) `((local ,st)) '())
21492150
,@ini
21502151
,@(map (lambda (i lhs)
2151-
(expand-forms
2152-
(if (vararg? lhs)
2153-
`(= ,(cadr lhs) (call (top rest) ,xx ,@(if (eq? i 0) '() `(,st))))
2154-
(lower-tuple-assignment
2155-
(if (= i (- n 1))
2156-
(list lhs)
2157-
(list lhs st))
2158-
`(call (top indexed_iterate)
2159-
,xx ,(+ i 1) ,@(if (eq? i 0) '() `(,st)))))))
2152+
(let ((lhs- (cond ((or (symbol? lhs) (ssavalue? lhs))
2153+
lhs)
2154+
((vararg? lhs)
2155+
(let ((lhs- (cadr lhs)))
2156+
(if (or (symbol? lhs-) (ssavalue? lhs-))
2157+
lhs
2158+
`(|...| ,(if (eventually-call? lhs-)
2159+
(gensy)
2160+
(make-ssavalue))))))
2161+
;; can't use ssavalues if it's a function definition
2162+
((eventually-call? lhs) (gensy))
2163+
(else (make-ssavalue)))))
2164+
(if (not (eq? lhs lhs-))
2165+
(if (vararg? lhs)
2166+
(set! end (cons (expand-forms `(= ,(cadr lhs) ,(cadr lhs-))) end))
2167+
(set! end (cons (expand-forms `(= ,lhs ,lhs-)) end))))
2168+
(expand-forms
2169+
(if (vararg? lhs-)
2170+
`(= ,(cadr lhs-) (call (top rest) ,xx ,@(if (eq? i 0) '() `(,st))))
2171+
(lower-tuple-assignment
2172+
(if (= i (- n 1))
2173+
(list lhs-)
2174+
(list lhs- st))
2175+
`(call (top indexed_iterate)
2176+
,xx ,(+ i 1) ,@(if (eq? i 0) '() `(,st))))))))
21602177
(iota n)
21612178
lhss)
2179+
,@(reverse end)
21622180
(unnecessary ,xx))))))
21632181

21642182
;; move an assignment into the last statement of a block to keep more statements at top level

test/syntax.jl

+11
Original file line numberDiff line numberDiff line change
@@ -2819,3 +2819,14 @@ macro m_nospecialize_unnamed_hygiene()
28192819
end
28202820

28212821
@test @m_nospecialize_unnamed_hygiene()(1) === Any
2822+
2823+
# https://github.com/JuliaLang/julia/issues/40574
2824+
@testset "no mutation while destructuring" begin
2825+
x = [1, 2]
2826+
x[2], x[1] = x
2827+
@test x == [2, 1]
2828+
2829+
x = [1, 2, 3]
2830+
x[3], x[1:2]... = x
2831+
@test x == [2, 3, 1]
2832+
end

0 commit comments

Comments
 (0)