Skip to content

Commit 490515d

Browse files
committed
allow vararg in lhs of assignment
1 parent d2856a3 commit 490515d

File tree

3 files changed

+136
-29
lines changed

3 files changed

+136
-29
lines changed

base/tuple.jl

+19
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,25 @@ iterate_and_index(x) = (destruct_iterate, select_first)
116116
# to make inference's life easier.
117117
iterate_and_index(::Nothing) = throw(MethodError(iterate, (nothing,)))
118118

119+
function _rest(itr, state, _)
120+
@_inline_meta
121+
state === () && return rest(itr)
122+
return rest(itr, state[1][2])
123+
end
124+
_rest(x::Union{Array,Tuple,NamedTuple,Pair}, _, i) = (@_inline_meta; rest(x, i))
125+
126+
rest(t::Tuple) = t
127+
rest(t::NTuple{N}, i::Int) where {N} = ntuple(x -> getfield(t, x+i-1), N-i+1)
128+
rest(a::Array, i::Int=1) = a[i:end]
129+
# The semantics of `collect` are weird. Better to write our own
130+
function rest(a::AbstractArray{T}, state...) where {T}
131+
v = Vector{T}(undef, 0)
132+
# assume only very few items are taken from the front
133+
sizehint!(v, length(a))
134+
return foldl(push!, Iterators.rest(a, state...), init=v)
135+
end
136+
rest(itr, state...) = Iterators.rest(itr, state...)
137+
119138
# Use dispatch to avoid a branch in first
120139
first(::Tuple{}) = throw(ArgumentError("tuple must be non-empty"))
121140
first(t::Tuple) = t[1]

src/julia-syntax.scm

+66-29
Original file line numberDiff line numberDiff line change
@@ -1422,7 +1422,8 @@
14221422
,@(reverse after)
14231423
(unnecessary (tuple ,@(reverse elts))))
14241424
(let ((L (car lhss))
1425-
(R (car rhss)))
1425+
;; rhss can be null iff L is a vararg
1426+
(R (if (null? rhss) '() (car rhss))))
14261427
(cond ((and (symbol-like? L)
14271428
(or (not (pair? R)) (quoted? R) (equal? R '(null)))
14281429
;; overwrite var immediately if it doesn't occur elsewhere
@@ -1434,6 +1435,16 @@
14341435
(cons (make-assignment L R) stmts)
14351436
after
14361437
(cons R elts)))
1438+
((vararg? L)
1439+
(if (null? (cdr lhss))
1440+
(let ((temp (make-ssavalue)))
1441+
`(block ,@(reverse stmts)
1442+
(= ,temp (tuple ,@rhss))
1443+
,@(reverse after)
1444+
(= ,(cadr L) ,temp)
1445+
(unnecessary (tuple ,@(reverse elts) (... ,temp)))))
1446+
(error (string "invalid \"...\" on non-final assignment location \""
1447+
(cadr L) "\""))))
14371448
((vararg? R)
14381449
(let ((temp (make-ssavalue)))
14391450
`(block ,@(reverse stmts)
@@ -2035,6 +2046,7 @@
20352046
(define (sides-match? l r)
20362047
;; l and r either have equal lengths, or r has a trailing ...
20372048
(cond ((null? l) (null? r))
2049+
((vararg? (car l)) #t)
20382050
((null? r) #f)
20392051
((vararg? (car r)) (null? (cdr r)))
20402052
(else (sides-match? (cdr l) (cdr r)))))
@@ -2044,34 +2056,59 @@
20442056
(expand-forms
20452057
(tuple-to-assignments lhss x))
20462058
;; (a, b, ...) = other
2047-
(let* ((xx (if (or (and (symbol? x) (not (memq x lhss)))
2048-
(ssavalue? x))
2049-
x (make-ssavalue)))
2050-
(ini (if (eq? x xx) '() (list (sink-assignment xx (expand-forms x)))))
2051-
(n (length lhss))
2052-
(funcs (make-ssavalue))
2053-
(iterate (make-ssavalue))
2054-
(index (make-ssavalue))
2055-
(st (gensy)))
2056-
`(block
2057-
,@ini
2058-
,(lower-tuple-assignment
2059-
(list iterate index)
2060-
`(call (top iterate_and_index) ,xx))
2061-
,.(map (lambda (i lhs)
2062-
(expand-forms
2063-
`(block
2064-
(= ,st (call ,iterate
2065-
,xx ,.(if (eq? i 0) '() `(,st))))
2066-
,(if (eventually-call? lhs)
2067-
(let ((val (gensy)))
2068-
`(block
2069-
(= ,val (call ,index ,st ,(+ i 1)))
2070-
(= ,lhs ,val)))
2071-
`(= ,lhs (call ,index ,st ,(+ i 1)))))))
2072-
(iota n)
2073-
lhss)
2074-
(unnecessary ,xx))))))
2059+
(begin
2060+
;; like memq, but if last element of lhss is (... sym),
2061+
;; check against sym instead
2062+
(define (in-lhs? x lhss)
2063+
(if (null? lhss)
2064+
#f
2065+
(let ((l (car lhss)))
2066+
(cond ((and (pair? l) (eq? (car l) '|...|))
2067+
(if (null? (cdr lhss))
2068+
(eq? (cadr l) x)
2069+
(error (string "invalid \"...\" on non-final assignment location \""
2070+
(cadr l) "\""))))
2071+
((eq? l x) #t)
2072+
(else (in-lhs? x (cdr lhss)))))))
2073+
(define (gensymified-assignment lhs rhs)
2074+
(if (eventually-call? lhs)
2075+
(let ((val (gensy)))
2076+
`(block
2077+
(= ,val ,rhs)
2078+
(= ,lhs ,val)))
2079+
`(= ,lhs ,rhs)))
2080+
(let* ((xx (if (or (and (not (in-lhs? x lhss)) (symbol? x))
2081+
(ssavalue? x))
2082+
x (make-ssavalue)))
2083+
(ini (if (eq? x xx) '() (list (sink-assignment xx (expand-forms x)))))
2084+
(n (length lhss))
2085+
(funcs (make-ssavalue))
2086+
(iterate (make-ssavalue))
2087+
(index (make-ssavalue))
2088+
(st (gensy)))
2089+
`(block
2090+
,@ini
2091+
,(lower-tuple-assignment
2092+
(list iterate index)
2093+
`(call (top iterate_and_index) ,xx))
2094+
,.(map (lambda (i lhs)
2095+
(expand-forms
2096+
(if (and (pair? lhs) (eq? (car lhs) '|...|))
2097+
(gensymified-assignment
2098+
(cadr lhs)
2099+
`(call (top _rest)
2100+
,xx
2101+
,(if (eq? i 0) '(tuple) `(tuple ,st))
2102+
,(+ i 1)))
2103+
`(block
2104+
(= ,st (call ,iterate
2105+
,xx ,.(if (eq? i 0) '() `(,st))))
2106+
,(gensymified-assignment
2107+
lhs
2108+
`(call ,index ,st ,(+ i 1)))))))
2109+
(iota n)
2110+
lhss)
2111+
(unnecessary ,xx)))))))
20752112
((typed_hcat)
20762113
(error "invalid spacing in left side of indexed assignment"))
20772114
((typed_vcat)

test/syntax.jl

+51
Original file line numberDiff line numberDiff line change
@@ -2306,3 +2306,54 @@ end
23062306
@test_throws ParseError("invalid operator \".<---\"") Meta.parse("1 .<--- 2")
23072307
@test_throws ParseError("invalid operator \"--\"") Meta.parse("a---b")
23082308
@test_throws ParseError("invalid operator \".--\"") Meta.parse("a.---b")
2309+
2310+
@testset "slurp in assignments" begin
2311+
res = begin x, y, z... = 1:7 end
2312+
@test res == 1:7
2313+
@test x == 1 && y == 2
2314+
@test z == Vector(3:7)
2315+
2316+
res = begin x, y, z... = [1, 2] end
2317+
@test res == [1, 2]
2318+
@test x == 1 && y == 2
2319+
@test z == Int[]
2320+
2321+
x = 1
2322+
res = begin x..., = x end
2323+
@test res == 1
2324+
@test x == 1
2325+
2326+
x, y, z... = 1:7
2327+
res = begin y, z, x... = z..., x, y end
2328+
@test res == ((3:7)..., 1, 2)
2329+
@test y == 3
2330+
@test z == 4
2331+
@test x == ((5:7)..., 1, 2)
2332+
2333+
res = begin x, _, y... = 1, 2 end
2334+
@test res == (1, 2)
2335+
@test x == 1
2336+
@test y == ()
2337+
2338+
res = begin x, y... = 1 end
2339+
@test res == 1
2340+
@test x == 1
2341+
@test y == Iterators.rest(1, nothing)
2342+
2343+
res = begin x, y, z... = 1, 2, 3:5 end
2344+
@test res == (1, 2, 3:5)
2345+
@test x == 1 && y == 2
2346+
@test z == (3:5,)
2347+
2348+
@test Meta.isexpr(Meta.@lower(begin a, b..., c = 1:3 end), :error)
2349+
@test Meta.isexpr(Meta.@lower(begin a, b..., c = 1, 2, 3 end), :error)
2350+
@test Meta.isexpr(Meta.@lower(begin a, b..., c... = 1, 2, 3 end), :error)
2351+
2352+
@test_throws BoundsError begin x, y, z... = 1:1 end
2353+
@test_throws BoundsError begin x, y, _, z... = 1, 2 end
2354+
2355+
car((a, d...)) = a
2356+
cdr((a, d...)) = d
2357+
@test car(1:3) == 1
2358+
@test cdr(1:3) == [2, 3]
2359+
end

0 commit comments

Comments
 (0)