Skip to content

Commit 6aede60

Browse files
committed
implement property destructuring
This currently only allows the form `(; a, b) = x` and lowers this to calls to `getproperty`. We could think about whether we want to allow specifying default values for properties as well, or even some kind of property renaming in the lhs. We could even allow slurping unused properties, but all of that sounds more difficult to work out in detail and potentially controversial, so I left this as an error for now. fixes #28579
1 parent 7647ab5 commit 6aede60

File tree

2 files changed

+105
-59
lines changed

2 files changed

+105
-59
lines changed

src/julia-syntax.scm

Lines changed: 76 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,6 +1950,66 @@
19501950
,@(map expand-forms (cddr e))))
19511951
(cons (car e) (map expand-forms (cdr e))))))
19521952

1953+
(define (expand-tuple-destruct lhss x)
1954+
(define (sides-match? l r)
1955+
;; l and r either have equal lengths, or r has a trailing ...
1956+
(cond ((null? l) (null? r))
1957+
((vararg? (car l)) #t)
1958+
((null? r) #f)
1959+
((vararg? (car r)) (null? (cdr r)))
1960+
(else (sides-match? (cdr l) (cdr r)))))
1961+
(if (and (pair? x) (pair? lhss) (eq? (car x) 'tuple) (not (any assignment? (cdr x)))
1962+
(not (has-parameters? (cdr x)))
1963+
(sides-match? lhss (cdr x)))
1964+
;; (a, b, ...) = (x, y, ...)
1965+
(expand-forms
1966+
(tuple-to-assignments lhss x))
1967+
;; (a, b, ...) = other
1968+
(begin
1969+
;; like memq, but if last element of lhss is (... sym),
1970+
;; check against sym instead
1971+
(define (in-lhs? x lhss)
1972+
(if (null? lhss)
1973+
#f
1974+
(let ((l (car lhss)))
1975+
(cond ((and (pair? l) (eq? (car l) '|...|))
1976+
(if (null? (cdr lhss))
1977+
(eq? (cadr l) x)
1978+
(error (string "invalid \"...\" on non-final assignment location \""
1979+
(cadr l) "\""))))
1980+
((eq? l x) #t)
1981+
(else (in-lhs? x (cdr lhss)))))))
1982+
;; in-lhs? also checks for invalid syntax, so always call it first
1983+
(let* ((xx (if (or (and (not (in-lhs? x lhss)) (symbol? x))
1984+
(ssavalue? x))
1985+
x (make-ssavalue)))
1986+
(ini (if (eq? x xx) '() (list (sink-assignment xx (expand-forms x)))))
1987+
(n (length lhss))
1988+
;; skip last assignment if it is an all-underscore vararg
1989+
(n (if (> n 0)
1990+
(let ((l (last lhss)))
1991+
(if (and (vararg? l) (underscore-symbol? (cadr l)))
1992+
(- n 1)
1993+
n))
1994+
n))
1995+
(st (gensy)))
1996+
`(block
1997+
,@(if (> n 0) `((local ,st)) '())
1998+
,@ini
1999+
,@(map (lambda (i lhs)
2000+
(expand-forms
2001+
(if (vararg? lhs)
2002+
`(= ,(cadr lhs) (call (top rest) ,xx ,@(if (eq? i 0) '() `(,st))))
2003+
(lower-tuple-assignment
2004+
(if (= i (- n 1))
2005+
(list lhs)
2006+
(list lhs st))
2007+
`(call (top indexed_iterate)
2008+
,xx ,(+ i 1) ,@(if (eq? i 0) '() `(,st)))))))
2009+
(iota n)
2010+
lhss)
2011+
(unnecessary ,xx))))))
2012+
19532013
;; move an assignment into the last statement of a block to keep more statements at top level
19542014
(define (sink-assignment lhs rhs)
19552015
(if (and (pair? rhs) (eq? (car rhs) 'block))
@@ -2079,67 +2139,24 @@
20792139
(call (top setproperty!) ,aa ,bb ,rr)
20802140
(unnecessary ,rr)))))
20812141
((tuple)
2082-
;; multiple assignment
20832142
(let ((lhss (cdr lhs))
20842143
(x (caddr e)))
2085-
(define (sides-match? l r)
2086-
;; l and r either have equal lengths, or r has a trailing ...
2087-
(cond ((null? l) (null? r))
2088-
((vararg? (car l)) #t)
2089-
((null? r) #f)
2090-
((vararg? (car r)) (null? (cdr r)))
2091-
(else (sides-match? (cdr l) (cdr r)))))
2092-
(if (and (pair? x) (pair? lhss) (eq? (car x) 'tuple) (not (any assignment? (cdr x)))
2093-
(not (has-parameters? (cdr x)))
2094-
(sides-match? lhss (cdr x)))
2095-
;; (a, b, ...) = (x, y, ...)
2096-
(expand-forms
2097-
(tuple-to-assignments lhss x))
2098-
;; (a, b, ...) = other
2099-
(begin
2100-
;; like memq, but if last element of lhss is (... sym),
2101-
;; check against sym instead
2102-
(define (in-lhs? x lhss)
2103-
(if (null? lhss)
2104-
#f
2105-
(let ((l (car lhss)))
2106-
(cond ((and (pair? l) (eq? (car l) '|...|))
2107-
(if (null? (cdr lhss))
2108-
(eq? (cadr l) x)
2109-
(error (string "invalid \"...\" on non-final assignment location \""
2110-
(cadr l) "\""))))
2111-
((eq? l x) #t)
2112-
(else (in-lhs? x (cdr lhss)))))))
2113-
;; in-lhs? also checks for invalid syntax, so always call it first
2114-
(let* ((xx (if (or (and (not (in-lhs? x lhss)) (symbol? x))
2115-
(ssavalue? x))
2116-
x (make-ssavalue)))
2117-
(ini (if (eq? x xx) '() (list (sink-assignment xx (expand-forms x)))))
2118-
(n (length lhss))
2119-
;; skip last assignment if it is an all-underscore vararg
2120-
(n (if (> n 0)
2121-
(let ((l (last lhss)))
2122-
(if (and (vararg? l) (underscore-symbol? (cadr l)))
2123-
(- n 1)
2124-
n))
2125-
n))
2126-
(st (gensy)))
2127-
`(block
2128-
,@(if (> n 0) `((local ,st)) '())
2129-
,@ini
2130-
,@(map (lambda (i lhs)
2131-
(expand-forms
2132-
(if (vararg? lhs)
2133-
`(= ,(cadr lhs) (call (top rest) ,xx ,@(if (eq? i 0) '() `(,st))))
2134-
(lower-tuple-assignment
2135-
(if (= i (- n 1))
2136-
(list lhs)
2137-
(list lhs st))
2138-
`(call (top indexed_iterate)
2139-
,xx ,(+ i 1) ,@(if (eq? i 0) '() `(,st)))))))
2140-
(iota n)
2141-
lhss)
2142-
(unnecessary ,xx)))))))
2144+
(if (has-parameters? lhss)
2145+
;; property destructuring
2146+
(if (length= lhss 1)
2147+
(let* ((xx (if (symbol-like? x) x (make-ssavalue)))
2148+
(ini (if (eq? x xx) '() (list (sink-assignment xx (expand-forms x))))))
2149+
`(block
2150+
,@ini
2151+
,@(map (lambda (field)
2152+
(if (not (symbol? field))
2153+
(error (string "invalid assignment location \"" (deparse lhs) "\"")))
2154+
(expand-forms `(= ,field (call (top getproperty) ,xx (quote ,field)))))
2155+
(cdar lhss))
2156+
(unnecessary ,xx)))
2157+
(error (string "invalid assignment location \"" (deparse lhs) "\"")))
2158+
;; multiple assignment
2159+
(expand-tuple-destruct lhss x))))
21432160
((typed_hcat)
21442161
(error "invalid spacing in left side of indexed assignment"))
21452162
((typed_vcat)

test/syntax.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2649,3 +2649,32 @@ end
26492649

26502650
# issue #38501
26512651
@test :"a $b $("str") c" == Expr(:string, "a ", :b, " ", Expr(:string, "str"), " c")
2652+
2653+
@testset "property destructuring" begin
2654+
res = begin (; num, den) = 1 // 2 end
2655+
@test res == 1 // 2
2656+
@test num == 1
2657+
@test den == 2
2658+
2659+
res = begin (; b, a) = (a=1, b=2, c=3) end
2660+
@test res == (a=1, b=2, c=3)
2661+
@test b == 2
2662+
@test a == 1
2663+
2664+
# could make this an error instead, but I think this is reasonable
2665+
res = begin (; a, b, a) = (a=5, b=6) end
2666+
@test res == (a=5, b=6)
2667+
@test a == 5
2668+
@test b == 6
2669+
2670+
@test_throws ErrorException (; a, b) = (x=1,)
2671+
2672+
@test Meta.isexpr(Meta.@lower(begin (a, b; c) = x end), :error)
2673+
@test Meta.isexpr(Meta.@lower(begin (a, b; c) = x, y end), :error)
2674+
@test Meta.isexpr(Meta.@lower(begin (; c, a.b) = x end), :error)
2675+
2676+
f((; a, b)) = a, b
2677+
@test f((b=3, a=4)) == (4, 3)
2678+
@test f((b=3, c=2, a=4)) == (4, 3)
2679+
@test_throws ErrorException f((;))
2680+
end

0 commit comments

Comments
 (0)