Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
Re: [PATCH] Fix bang flag in ${(!)ref::=var} substitutions
- X-seq: zsh-workers 55026
- From: Philippe Altherr <philippe.altherr@xxxxxxxxx>
- To: Bart Schaefer <schaefer@xxxxxxxxxxxxxxxx>
- Cc: Zsh hackers list <zsh-workers@xxxxxxx>
- Subject: Re: [PATCH] Fix bang flag in ${(!)ref::=var} substitutions
- Date: Thu, 23 Jul 2026 17:28:47 +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=PIEvMSfGq/Py1G+3s/tng/3adm5zxf7Od/kiSoKtjVk=; fh=vuDAGjptCPc/P1/HEZ3j98yPJEW1XjuEoEAmmwDS/+U=; b=fuY2wUccwT3N2JS2NfDRKeeekg+zqkPOYPvcI2YKHC5xYxHFDds0S2jhuDn4N7OI5U O/nvUV5rT/fToBnsm+3Wgspu/wA7seeL8gpXX62Q96mbDqu4LkT27L8/koi8tB3FmGMj irhM/z7WBMFxacg04Kj0jZPWBXQpdt07CleYXkPVpVGghVxsL47d5bGU5ByYC/EB725t uABjFHvgFw27kQFKFWpk7nhAKGgE2iAyxIIZPP73/BR+jczZVjziAmxg1HwH3vgUBNd1 qzQDB344XJ97POT6J2OCuFeWoVmRgS7j5Hl7ptWBXb8vuZWQBXssseetOtuf4lr6jRCu Dv2Q==; darn=zsh.org
- Arc-seal: i=1; a=rsa-sha256; t=1784820539; cv=none; d=google.com; s=arc-20260327; b=OdoCsT5t4t1IbGLyIHQqhZXSyVu0LaiNKGOwWsbcYwiRAZCEzXMBrPWeK27yMghffD Opu6lSscFLBEdCewAIhSjg87D48AXmV2elVGNdlA4W8UlNFl6iLYRJC83gEWHw+LUBHP 6cyeOHoVoMfmUU//iBK3e+oxMRDeLZQLAO4C+reCsDZKrkM/WvPwNtDgKMbDXhhCot47 7yaMyxfSOEBmEANpbULbJmG7mo4KpwTVz2uOP99uaDd+8gKUPPEuykRmVYNGDoc9T1C0 LyGLUTISTEvP1gaqCGpUJIV4es71GqoxSv6Tcnb+Up1TwyQvaVzD1sszWenu6u0ildIK A6Jg==
- Archived-at: <https://zsh.org/workers/55026>
- In-reply-to: <CAH+w=7ZRs+4LpUNkTzpVgbYHndfNcUPreOicmBS4Z9=0Op=FaA@mail.gmail.com>
- List-id: <zsh-workers.zsh.org>
- References: <CAGdYchtt-6ybEWH0q70maUO2D3ZfieNdVN_68-F2MVohjdk77Q@mail.gmail.com> <CAH+w=7ZRs+4LpUNkTzpVgbYHndfNcUPreOicmBS4Z9=0Op=FaA@mail.gmail.com>
On Fri, Jul 10, 2026 at 5:06 AM Philippe Altherr
<philippe.altherr@xxxxxxxxx> wrote:
>
> The documentation states that with the substitution flag (!) the reference itself is examined, not its referent, but this fails in ${(!)ref::=var} substitutions:
I'm unsure what to do with this one.
Ksh doesn't have ${x::=y} so there's no direct comparison.
With "ref" undeclared or with `typeset -n ref` (placeholder),
${!ref:=var} ksh always returns "ref" and ignores the assignment.
So we're in a bit of uncharted territory here. It's not clear that
::= should alter "ref".
As discussed in
workers/55013, ksh's ! doesn't have much to do with Zsh's (!). Afaik, there is no equivalent of
Zsh's (!) in ksh. So we are on our own.
Is this a precedence issue? I.e., should ${(!)ref::=var} be
equivalent to ${(!)${ref::=var}} ? (Which is the current behavior,
right?)
Indeed, that produces the same result but in my opinion that's definitely not what we want for the same reason we don't want ${(!)ref} to be the same as ${(!)${ref}}, which is the same as ${ref}.
My reasoning is pretty simple. Zsh supports many different kinds of expansion forms, like ${name}, ${name-word}, ${name+word}, ${name#pattern}. In all of these, if (!) is used, the parameter "name" is NOT dereferenced and it's its own value (the name of the referred variable) that is used. For the same reason, ${name=word}, ${name:=word} and ${name::=word} should all act on that same value. Thus, ${(!)ref::=word} should always have the same effect as the assignment in "typeset -gn ref=word" and then expand to "${(!)ref}".
Implementation-wise the patch simply makes it such that paramsubst can pass SCANPM_NONAMEREF, or an equivalent of it, not only to fetchvalue but also to assignsparam and assignaparam.
Below is an updated patch obtained after rebasing onto HEAD.
Philippe
diff --git a/Src/params.c b/Src/params.c
index 7409ff530..7019ec8e0 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -3143,6 +3143,7 @@ assignsparam(char *s, char *val, int flags)
size_t lvar;
mnumber lhs, rhs;
int sstart, created = 0;
+ int scanflags = flags & ASSPM_NONAMEREF ? SCANPM_NONAMEREF : 0;
if (!isident(s)) {
zerr("not an identifier: %s", s);
@@ -3153,7 +3154,7 @@ assignsparam(char *s, char *val, int flags)
queue_signals();
if ((ss = strchr(s, '['))) {
*ss = '\0';
- if (!(v = getvalue(&vbuf, &s, 1))) {
+ if (!(v = fetchvalue(&vbuf, &s, 1, scanflags))) {
createparam(t, PM_ARRAY);
created = 1;
} else {
@@ -3174,7 +3175,7 @@ assignsparam(char *s, char *val, int flags)
*ss = '[';
v = NULL;
} else {
- if (!(v = getvalue(&vbuf, &s, 1))) {
+ if (!(v = fetchvalue(&vbuf, &s, 1, scanflags))) {
createparam(t, PM_SCALAR);
created = 1;
} else if ((((v->pm->node.flags & PM_ARRAY) &&
@@ -3192,7 +3193,7 @@ assignsparam(char *s, char *val, int flags)
v = NULL;
}
}
- if (!v && !(v = getvalue(&vbuf, &t, 1))) {
+ if (!v && !(v = fetchvalue(&vbuf, &t, 1, scanflags))) {
zsfree(val);
unqueue_signals();
/* errflag |= ERRFLAG_ERROR; */
@@ -3305,6 +3306,7 @@ assignaparam(char *s, char **val, int flags)
char *ss;
int created = 0;
int may_warn_about_nested_vars = 1;
+ int scanflags = flags & ASSPM_NONAMEREF ? SCANPM_NONAMEREF : 0;
if (!isident(s)) {
zerr("not an identifier: %s", s);
@@ -3315,7 +3317,7 @@ assignaparam(char *s, char **val, int flags)
queue_signals();
if ((ss = strchr(s, '['))) {
*ss = '\0';
- if (!(v = getvalue(&vbuf, &s, 1))) {
+ if (!(v = fetchvalue(&vbuf, &s, 1, scanflags))) {
createparam(t, PM_ARRAY);
created = 1;
} else {
@@ -3332,7 +3334,7 @@ assignaparam(char *s, char **val, int flags)
}
v = NULL;
} else {
- if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING))) {
+ if (!(v = fetchvalue(&vbuf, &s, 1, scanflags | SCANPM_ASSIGNING))) {
createparam(t, PM_ARRAY);
created = 1;
} else if (v->pm->node.flags & PM_NAMEREF) {
@@ -3363,7 +3365,7 @@ assignaparam(char *s, char **val, int flags)
}
}
if (!v)
- if (!(v = fetchvalue(&vbuf, &t, 1, SCANPM_ASSIGNING))) {
+ if (!(v = fetchvalue(&vbuf, &t, 1, scanflags | SCANPM_ASSIGNING))) {
unqueue_signals();
freearray(val);
/* errflag |= ERRFLAG_ERROR; */
diff --git a/Src/subst.c b/Src/subst.c
index 1b75c035a..254408ea6 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -3248,6 +3248,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
if (vunset) {
char sav = *idend;
int l, split_flags;
+ int assign_flags = ASSPM_WARN |
+ (hkeys & SCANPM_NONAMEREF ? ASSPM_NONAMEREF : 0);
*idend = '\0';
val = dupstring(s);
@@ -3303,7 +3305,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
if (pm)
aval = paramvalarr(pm->gsu.h->getfn(pm), hkeys|hvals);
} else {
- Param pm = setaparam(idbeg, a);
+ Param pm = assignaparam(idbeg, a, assign_flags);
if (pm) {
struct value vbuf = { 0 };
char *p = idbeg;
@@ -3317,7 +3319,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
arrasg = 0;
} else {
untokenize(val);
- Param pm = setsparam(idbeg, ztrdup(val));
+ Param pm = assignsparam(idbeg, ztrdup(val), assign_flags);
if (pm) {
/* this check isn't needed for correctness, but array values
* aren't affected by SUBST flags anyway */
diff --git a/Src/zsh.h b/Src/zsh.h
index 1f1ecc3e6..720c664cc 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -2140,7 +2140,9 @@ enum {
* This is normal for associative arrays but variant behaviour for
* normal arrays.
*/
- ASSPM_KEY_VALUE = 1 << 4
+ ASSPM_KEY_VALUE = 1 << 4,
+ /* Named references are not followed */
+ ASSPM_NONAMEREF = 1 << 5
};
/* node for named directory hash table (nameddirtab) */
diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst
index 5082d1011..b6349ebf6 100644
--- a/Test/K01nameref.ztst
+++ b/Test/K01nameref.ztst
@@ -2349,6 +2349,50 @@ F:converting from association/array to string should work here too
>B:16#FF
>C:16#FF
+ show() { printf "%-13s: %-3s - %-3s - %-3s\n" "$@" }
+ typeset var=aaa
+ typeset -n refU refN="" refS=var
+ show '${refX}' ${(q!)refU} ${(q!)refN} ${(q!)refS}
+ show '${+refX}' ${(q!)+refU} ${(q!)+refN} ${(q!)+refS}
+ show '${refX+1}' ${(q!)refU+1} ${(q!)refN+1} ${(q!)refS+1}
+ show '${refX:+1}' ${(q!)refU:+1} ${(q!)refN:+1} ${(q!)refS:+1}
+ echo
+ show '${refX-bbb}' ${(q!)refU-bbb} ${(q!)refN-bbb} ${(q!)refS-bbb}
+ show '${refX=bbb}' ${(q!)refU=bbb} ${(q!)refN=bbb} ${(q!)refS=bbb}
+ show '${refX}' ${(q!)refU} ${(q!)refN} ${(q!)refS}
+ show '${var}' ${var}
+ echo
+ typeset -n refU refN="" refS=var
+ show '${refX:-ccc}' ${(q!)refU:-ccc} ${(q!)refN:-ccc} ${(q!)refS:-ccc}
+ show '${refX:=ccc}' ${(q!)refU:=ccc} ${(q!)refN:=ccc} ${(q!)refS:=ccc}
+ show '${refX}' ${(q!)refU} ${(q!)refN} ${(q!)refS}
+ show '${var}' ${var}
+ echo
+ typeset -n refU refN="" refS=var
+ show '${refX::=ddd}' ${(q!)refU::=ddd} ${(q!)refN::=ddd} ${(q!)refS::=ddd}
+ show '${refX}' ${(q!)refU} ${(q!)refN} ${(q!)refS}
+ show '${var}' ${var}
+ unfunction show
+0:reference itself is examined in substitutions with the ! flag
+>${refX} : '' - '' - var
+>${+refX} : 0 - 1 - 1
+>${refX+1} : '' - 1 - 1
+>${refX:+1} : '' - '' - 1
+>
+>${refX-bbb} : bbb - '' - var
+>${refX=bbb} : bbb - '' - var
+>${refX} : bbb - '' - var
+>${var} : aaa - -
+>
+>${refX:-ccc} : ccc - ccc - var
+>${refX:=ccc} : ccc - ccc - var
+>${refX} : ccc - ccc - var
+>${var} : aaa - -
+>
+>${refX::=ddd}: ddd - ddd - ddd
+>${refX} : ddd - ddd - ddd
+>${var} : aaa - -
+
typeset -n ref1
setopt allexport
ref1=var
Messages sorted by:
Reverse Date,
Date,
Thread,
Author