Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
Re: BUG: "return 0 && x=abc" does not skip parameter assignment
- X-seq: zsh-workers 55205
- From: Philippe Altherr <philippe.altherr@xxxxxxxxx>
- To: Takaaki Konno <re_c25@xxxxxxxxxxx>
- Cc: zsh-workers@xxxxxxx
- Subject: Re: BUG: "return 0 && x=abc" does not skip parameter assignment
- Date: Sun, 11 Oct 2026 01:58:36 +0200
- Arc-authentication-results: i=1; mx.google.com; arc=none
- Arc-message-signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:dkim-signature; bh=m/EC7c24Yt1V9g9mh7/+V3HCv1am5SPetix0F3wfpx4=; fh=V06wT4AwKWzjSkKtaS+lVrAPbOitrxjPi3c64KJKv48=; b=bMefPFGVlV0zH6peb7F9qPgvBKBa0GoF5tcJcF+tP45/YCtd7SFUuaDVCNNIIXIJtG M0gWWPn5LSuLMD2abyZMK2RH2D2/ni4yXnp4Hg8di2pPEMvegtFouovwr9taQsndhmr8 fU3CPeDjc1z+zK4reilx9Xdx1kOWaXbUHPbYsP8afMqsVRaajzdQdgD2d+QZ5TejXU9B En4QwxcW9PCPj8uJGRsmyqKwdJlugbFnsUCjZblw7eABgpvMd7D+mEu2MRe0EgTssR5n hamjctKmM3fooKvNh0jnOMM+3t/zaAe2dqv17mwZPA3StTYQd8n+HRO0w7No76t2DBGx tBXw==; darn=zsh.org
- Arc-seal: i=1; a=rsa-sha256; t=1791676728; cv=none; d=google.com; s=arc-20260327; b=ghNO22+kGbARJUB/M/CwF6fnzJEU/JB/eHlEzWCF7TOw7Qf7F7dnnDINiPECzpiq5V HDLmoLZwpI0WDZKS8JgS2+TPjQlGzjg8wfVTDVd0Kr6iaD6IlfLXl8cH9OdJZqatiTI2 iPz2Gjas+2mLmnSlTyGVX//ZNwsSuMxZJCEJ5iSnE2oSdd/2Pjq7L8CmDl13LLFn8wPG eaLs7JZDbtj8K58yMVRSaB/swGWHwlPQ7Eh5ekTF4WTaIOHQQjTr9A8GHuM+jLp8ZSid eWc/giaZyl32Haou1tV7WZOS3yDCvG0aGkZe+/trmPnmzB0uUT6nGmpJK1QDE4tAmtxc rOVA==
- Archived-at: <https://zsh.org/workers/55205>
- In-reply-to: <423185588.916905.1791638837238@mail.yahoo.co.jp>
- List-id: <zsh-workers.zsh.org>
- References: <423185588.916905.1791638837238.ref@mail.yahoo.co.jp> <423185588.916905.1791638837238@mail.yahoo.co.jp>
Below is a patch that fixes the problem and a few additional issues.
With the latest version of zsh, the following works:
$ () { while true; do return 1 || v=X; done }; echo v=$v
v=
but the version without loop fails to shortcut the assignment:
$ () { return 1 || v=X }; echo v=$v
v=X
This is because the implementation of return (and exit)
pretends to perform a break, which in my opinion is bogus. It's also apparently unnecessary. All implementations of loops in
loop.c take the retflag into account. Removing that code only breaks the test that was meant to
test the return shortcutting described here.
To fix the second example above, the code implementing the shortcutting of && and || needs to take not only break/continue statements into account but also return statements. In addition to that, the current shortcutting for break/continue statements doesn't shortcut enough. The shortcutting of && only shortcuts further && operations but not || operation (and vice-versa). Thus, the following currently fails:
$ while true; do break && false || v=X; done; echo v=$v
v=X
Finally, ! shortcutting only takes return statements into account but not break/continue statements. The following works
$ () { ! return }; echo $?
0
but not this:
$ while true; do ! break; done; echo $?
1
The following patch fixes all these issues.
Philippe
diff --git a/Src/builtin.c b/Src/builtin.c
index 64a27511f..0506d8d33 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -5811,7 +5811,6 @@ bin_break(char *name, char **argv, UNUSED(Options ops), int func)
if ((isset(INTERACTIVE) && isset(SHINSTDIN))
|| locallevel || sourcelevel) {
retflag = 1;
- breaks = loops;
lastval = num;
if (trap_state == TRAP_STATE_PRIMED && trap_return == -2
/*
@@ -5851,7 +5850,6 @@ bin_break(char *name, char **argv, UNUSED(Options ops), int func)
if (trap_state)
trap_state = TRAP_STATE_FORCE_RETURN;
retflag = 1;
- breaks = loops;
exit_pending = 1;
exit_level = locallevel;
exit_val = num;
diff --git a/Src/exec.c b/Src/exec.c
index b0794abab..b1a0652da 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -1558,12 +1558,14 @@ execlist(Estate state, int dont_change_job, int exiting)
* we find a sublist followed by ORNEXT. */
if (((WC_SUBLIST_FLAGS(code) & WC_SUBLIST_SIMPLE) ?
execsimple(state) :
- execpline(state, code, Z_SYNC, 0)) || breaks) {
+ execpline(state, code, Z_SYNC, 0)) ||
+ breaks || retflag) {
state->pc = next;
code = *state->pc++;
next = state->pc + WC_SUBLIST_SKIP(code);
while (wc_code(code) == WC_SUBLIST &&
- WC_SUBLIST_TYPE(code) == WC_SUBLIST_AND) {
+ (breaks || retflag ||
+ WC_SUBLIST_TYPE(code) == WC_SUBLIST_AND)) {
state->pc = next;
code = *state->pc++;
next = state->pc + WC_SUBLIST_SKIP(code);
@@ -1591,12 +1593,14 @@ execlist(Estate state, int dont_change_job, int exiting)
* we find a sublist followed by ANDNEXT. */
if (!(((WC_SUBLIST_FLAGS(code) & WC_SUBLIST_SIMPLE) ?
execsimple(state) :
- execpline(state, code, Z_SYNC, 0))) || breaks) {
+ execpline(state, code, Z_SYNC, 0))) ||
+ breaks || retflag) {
state->pc = next;
code = *state->pc++;
next = state->pc + WC_SUBLIST_SKIP(code);
while (wc_code(code) == WC_SUBLIST &&
- WC_SUBLIST_TYPE(code) == WC_SUBLIST_OR) {
+ (breaks || retflag ||
+ WC_SUBLIST_TYPE(code) == WC_SUBLIST_OR)) {
state->pc = next;
code = *state->pc++;
next = state->pc + WC_SUBLIST_SKIP(code);
@@ -2036,7 +2040,7 @@ execpline(Estate state, wordcode slcode, int how, int last1)
}
else
unqueue_signals();
- if ((slflags & WC_SUBLIST_NOT) && !errflag && !retflag)
+ if ((slflags & WC_SUBLIST_NOT) && !errflag && !retflag && !breaks)
lastval = !lastval;
}
if (!pline_level)
diff --git a/Test/A01grammar.ztst b/Test/A01grammar.ztst
index eea6ede2b..db48de8a9 100644
--- a/Test/A01grammar.ztst
+++ b/Test/A01grammar.ztst
@@ -982,38 +982,72 @@ F:its expectations.
fn
1:! does not affect return status of explicit return
+ for x in 1; do
+ ! continue
+ done
+0:! does not affect status of explicit continue
+
msg=unset
- for x in 1 2 3 4 5; do
+ for x in 1; do
continue && msg=set && print Not executed
print Not executed, neither.
done
- print $msg
-0:continue causes immediate continuation
->unset
+ print $?-$msg
+0:continue causes immediate continuation (immediate assign)
+>0-unset
+
+ msg=unset
+ for x in 1; do
+ continue && false || msg=set && print Not executed
+ print Not executed, neither.
+ done
+ print $?-$msg
+0:continue causes immediate continuation (assign after operator change)
+>0-unset
msg=unset
() {
- return && msg=set && print Not executed
- print Not executed, not nor neither.
+ return 7 || msg=set && print Not executed
+ print Not executed, neither.
}
- print $msg
-0:return causes immediate return
->unset
+ print $?-$msg
+0:return causes immediate return (immediate assign)
+>7-unset
msg=unset
- for x in 1 2 3 4 5; do
+ () {
+ return 7 || true && msg=set && print Not executed
+ print Not executed, neither.
+ }
+ print $?-$msg
+0:return causes immediate return (assign after operator change)
+>7-unset
+
+ $ZTST_testdir/../Src/zsh -fc '
+ msg=unset
+ () {
+ return 7 || msg=set && print Not executed
+ print Not executed, neither.
+ }
+ print $?-$msg
+ '
+0:return causes immediate return (no enclosing loops)
+>7-unset
+
+ msg=unset
+ for x in 1; do
! continue || msg=set && print Not executed
print Not executed, neither.
done
- print $msg
+ print $?-$msg
0:! continue causes immediate continuation
->unset
+>0-unset
msg=unset
() {
- ! return || msg=set && print Not executed
- print Not executed, not nor neither.
+ ! return 7 && msg=set && print Not executed
+ print Not executed, neither.
}
- print $msg
+ print $?-$msg
0:! return causes immediate return
->unset
+>7-unset
Messages sorted by:
Reverse Date,
Date,
Thread,
Author