Skip to content

Commit 211b974

Browse files
committed
Allow chained assignment in setv
1 parent 5088c3b commit 211b974

File tree

6 files changed

+73
-14
lines changed

6 files changed

+73
-14
lines changed

NEWS.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
.. default-role:: code
22

3+
Unreleased
4+
======================================================================
5+
6+
Supports Python 3.x – Python 3.y
7+
8+
New Features
9+
------------------------------
10+
* `setv` now supports chained assignment with `:chain`.
11+
312
1.1.0 ("Business Hugs", released 2025-05-08)
413
======================================================================
514

docs/api.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,12 @@ Assignment, mutation, and annotation
270270
(print letter1 letter2 (hy.repr others))
271271
; => a b ["c" "d" "e" "f" "g"]
272272

273+
Finally, as of Hy 1.2.0, you can precede an assignment pair with the keyword ``:chain`` to assign the same value multiple times to each of several targets. This construct compiles to a chained assignment in Python. ::
274+
275+
(setv :chain [x y z] 0)
276+
(print x y z)
277+
; => 0 0 0
278+
273279
See :hy:func:`let` to simulate more traditionally Lispy block-level scoping.
274280

275281
.. hy:macro:: (setx [target value])

hy/core/result_macros.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -481,23 +481,27 @@ def compile_augassign_expression(compiler, expr, root, target, values):
481481
# ------------------------------------------------
482482

483483

484-
@pattern_macro("setv", [many(maybe_annotated(FORM) + FORM)])
485-
@pattern_macro("setx", [times(1, 1, SYM + FORM)])
484+
@pattern_macro("setv", [many(
485+
(tag("chained", sym(":chain") + brackets(oneplus(FORM))) |
486+
tag("maybe_annotated", maybe_annotated(FORM))) +
487+
FORM)])
488+
@pattern_macro("setx", [times(1, 1, tag("setx", SYM) + FORM)])
486489
def compile_def_expression(compiler, expr, root, decls):
487490
if not decls:
488491
return asty.Constant(expr, value=None)
489492

490493
result = Result()
491-
is_assignment_expr = root == "setx"
492-
for decl in decls:
493-
if is_assignment_expr:
494-
ann = None
495-
target, value = decl
496-
else:
497-
(target, ann), value = decl
498494

495+
for (ttag, target), value in decls:
496+
ann = None
497+
if ttag == "chained":
498+
[target] = target
499+
if ttag == "maybe_annotated":
500+
target, ann = target
499501
result += compile_assign(
500-
compiler, ann, target, value, is_assignment_expr=is_assignment_expr
502+
compiler, ann, target, value,
503+
is_assignment_expr = ttag == "setx",
504+
chained = ttag == "chained"
501505
)
502506
return result
503507

@@ -521,7 +525,7 @@ def compile_basic_annotation(compiler, expr, root, target, ann):
521525

522526

523527
def compile_assign(
524-
compiler, ann, target, value, *, is_assignment_expr=False, let_scope=None
528+
compiler, ann, target, value, *, is_assignment_expr=False, chained=False, let_scope=None
525529
):
526530
# Ensure that assignment expressions have a result and no annotation.
527531
assert not is_assignment_expr or (value is not None and ann is None)
@@ -541,13 +545,15 @@ def compile_assign(
541545
# Throw away .expr to ensure that (setv ...) returns None.
542546
result.expr = None
543547
else:
544-
st_target = compiler._storeize(target, compiler.compile(target))
548+
st_targets = [
549+
compiler._storeize(t, compiler.compile(t))
550+
for t in (target if chained else [target])]
545551

546552
if ann is not None:
547553
ann_result = compiler.compile(ann)
548554
result = ann_result + result
549555

550-
target_kwarg = dict(target = st_target)
556+
target_kwarg = dict(target = st_targets[0])
551557
if is_assignment_expr:
552558
node = asty.NamedExpr
553559
elif ann is not None:
@@ -559,7 +565,7 @@ def compile_assign(
559565
)
560566
else:
561567
node = asty.Assign
562-
target_kwarg = dict(targets = [st_target])
568+
target_kwarg = dict(targets = st_targets)
563569

564570
result += node(
565571
target if hasattr(target, "start_line") else result,

tests/native_tests/setv.hy

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,35 @@
105105
(an (setv x.eggs "ham"))
106106
(assert (not (hasattr x "eggs")))
107107
(assert (= l [["eggs" "ham"]])))
108+
109+
110+
(defn test-setv-chain []
111+
112+
(setv :chain [a b c] 3)
113+
(assert (= a b c 3))
114+
115+
(setv v1 1 :chain [v2 v3] 2 v4 4)
116+
(assert (= v1 1))
117+
(assert (= v2 2))
118+
(assert (= v3 2))
119+
(assert (= v4 4))
120+
121+
(setv :chain [[y #* z w] x [a b c d]] "abcd")
122+
(assert (= [y z w] ["a" ["b" "c"] "d"]))
123+
(assert (= x "abcd"))
124+
(assert (= [a b c d] ["a" "b" "c" "d"]))
125+
126+
(setv l (* [0] 5))
127+
(setv calls [])
128+
(defn f [i]
129+
(.append calls [i (list l)])
130+
i)
131+
(setv :chain
132+
[(get l (f 1)) (get l (f 2)) (get l (f 3))]
133+
(f 9))
134+
(assert (= calls [
135+
[9 [0 0 0 0 0]]
136+
[1 [0 0 0 0 0]]
137+
[2 [0 9 0 0 0]]
138+
[3 [0 9 9 0 0]]]))
139+
(assert (= l [0 9 9 9 0])))

tests/resources/pydemo.hy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ Call me Ishmael. Some years ago—never mind how long precisely—having little
4343
(import itertools [cycle])
4444
(setv mygenexpr (gfor x (cycle [1 2 3]) :if (!= x 2) x))
4545
46+
(setv [unpacked1 #* repacked unpacked2] "WXYZ")
47+
(setv :chain [chained1 chained2 chained3] 77)
48+
4649
(setv attr-ref str.upper)
4750
(setv subscript (get "hello" 2))
4851
(setv myslice (cut "hello" 1 None 2))

tests/test_hy2py.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ def assert_stuff(m):
8989
assert type(m.mygenexpr) is type(x for x in [1, 2, 3])
9090
assert list(itertools.islice(m.mygenexpr, 5)) == [1, 3, 1, 3, 1]
9191

92+
assert (m.unpacked1, m.repacked, m.unpacked2) == ("W", ["X", "Y"], "Z")
93+
assert m.chained1 == m.chained2 == m.chained3 == 77
94+
9295
assert m.attr_ref is str.upper
9396
assert m.subscript == "l"
9497
assert m.myslice == "el"

0 commit comments

Comments
 (0)