Re: Issue with VAR=foo cmd where VAR is a named reference

Philippe Altherr <[email protected]> Fri, 10 Jul 2026 15:38:48 +0200
Newsgroups gmane.comp.shells.zsh.devel
Message-ID <CAGdYchscFj-hUdKvuPNC0c05MNE9xHGRiW1mRMFLpb5JbrVLsw@mail.gmail.com>
Here is a proposal that is easy to implement and that avoids the existing
problems (export of referred variables, broken restoration of hidden
referred variables).

Bart suggested that "var=foo cmd" ought to be the same as running "cmd" in
a scope augmented with an exported local "var" parameter: "() { local -x
var=foo; cmd }". However, this is incompatible with the current behavior.
Indeed, the type of the assigned parameters is preserved. For example,
"typeset -i var; var=10+1 printenv var" is equivalent to "typeset -i var;
() { local -x -i var=10+1; printenv var }". My proposal is to do the same
for references, such that "typeset -n ref=foo; ref=bar cmd" is equivalent
to "typeset -n ref=foo; () { local -n ref=bar; cmd }".

Inline assignments already differ from regular assignments by the fact that
their changes are only temporary and only visible in the affected command.
With my proposal they would additionally differ by the fact that no
reference resolution is performed on the assigned parameters. In that
sense, they would behave in the same way as for loops (e.g., "typeset -n
ref=var; for ref in a b c; do … done" successively assigns "a", "b", and
"c" to "ref" and not "var"). This proposal also matches ksh's behavior (in
dynamic scopes):

% ksh -c 'function f { typeset -p ref; }; function g { typeset -n ref=foo;
ref=bar f; typeset -p ref; }; g'
typeset -n -x ref=bar
typeset -n ref=foo

- In inline assignments, assign the reference, not its referent
<https://github.com/paltherr/zsh/compare/no-deref-in-inline-assignments-start...paltherr:zsh:no-deref-in-inline-assignments>
  (depends on workers/54941 <https://zsh.org/workers/54941> (not really
required but built on top of it to avoid later merge conflicts),
workers/54942 <https://zsh.org/workers/54942>, and workers/54943
<https://zsh.org/workers/54943>)

Philippe
no-deref-in-inline-assignments.txt (text/plain, 2.6 KB)
diff --git a/Src/exec.c b/Src/exec.c
index 20caf40d1..a7a59f012 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -2571,6 +2571,7 @@ addvars(Estate state, Wordcode pc, int addflags)
      * is implicitly scoped.
      */
     flags = !(addflags & ADDVAR_RESTORE) ? ASSPM_WARN : 0;
+    flags |= addflags & ADDVAR_EXPORT ? ASSPM_NONAMEREF : 0;
     xtr = isset(XTRACE);
     if (xtr) {
 	printprompt4();
@@ -4477,7 +4478,7 @@ save_params(Estate state, Wordcode pc, LinkList *restore_p, LinkList *remove_p)
 	char *ss = itype_end(s, INAMESPC, 0);
 	int slen = *ss == '[' || *ss == Inbrack ? ss - s : strlen(s);
 	addlinknode(*remove_p, s = dupstring_wlen(s, slen));
-	if ((pm = (Param) paramtab->getnode(paramtab, s))) {
+	if ((pm = (Param) realparamtab->getnode2(realparamtab, s))) {
 	    Param tpm = NULL;
 	    if (pm->env)
 		delenv(pm);
@@ -4525,7 +4526,7 @@ restore_params(LinkList restorelist, LinkList removelist)
 
     /* remove temporary parameters */
     while ((s = (char *) ugetnode(removelist))) {
-	if ((pm = (Param) paramtab->getnode(paramtab, s)) &&
+	if ((pm = (Param) realparamtab->getnode2(realparamtab, s)) &&
 	    !(pm->node.flags & PM_SPECIAL)) {
 	    pm->node.flags &= ~PM_READONLY;
 	    unsetparam_pm(pm, 0, 0);
@@ -4536,7 +4537,8 @@ restore_params(LinkList restorelist, LinkList removelist)
 	/* restore saved parameters */
 	while ((pm = (Param) ugetnode(restorelist))) {
 	    if (pm->node.flags & PM_SPECIAL) {
-		Param tpm = (Param) paramtab->getnode(paramtab, pm->node.nam);
+		Param tpm =
+		    (Param) realparamtab->getnode2(realparamtab, pm->node.nam);
 
 		DPUTS(!tpm || PM_TYPE(pm->node.flags) != PM_TYPE(tpm->node.flags) ||
 		      !(pm->node.flags & PM_SPECIAL),
@@ -4565,7 +4567,7 @@ restore_params(LinkList restorelist, LinkList removelist)
 		}
 		pm = tpm;
 	    } else {
-		paramtab->addnode(paramtab, pm->node.nam, pm);
+		realparamtab->addnode(realparamtab, pm->node.nam, pm);
 	    }
 	    if ((pm->node.flags & PM_EXPORTED) && ((s = getsparam(pm->node.nam))))
 		addenv(pm, s);
diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst
index 1ed21906f..c9d920196 100644
--- a/Test/K01nameref.ztst
+++ b/Test/K01nameref.ztst
@@ -2402,4 +2402,20 @@ F:converting from association/array to string should work here too
 >typeset -n ref1=var
 >typeset -n ref2
 
+ show() { typeset -p var ref }
+ typeset var=foo
+ typeset -n ref=var
+ ref=bar show
+ typeset -p var ref
+ echo var=$(ref=bar printenv var)
+ echo ref=$(ref=bar printenv ref)
+ unfunction show
+0:reference inline assignment assigns the reference
+>typeset -g var=foo
+>typeset -g -n ref=bar
+>typeset var=foo
+>typeset -n ref=var
+>var=
+>ref=
+
 %clean