[Language Design] How should references to localized special parameters behave?

Philippe Altherr <[email protected]> Tue, 4 Aug 2026 21:42:01 +0200
Newsgroups gmane.comp.shells.zsh.devel
Message-ID <CAGdYchvbQ33OPjoQ4c-yZ3SscmTOpSk=u7m2zFyOkwgOazxFbg@mail.gmail.com>
--000000000000e4e46906583dd9c0
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

What should the following script print?

typeset -n home=3DHOME
HOME=3D/bin; cd; pwd
function fn {
  typeset HOME=3D/usr; cd; pwd
  home=3D/var; cd; pwd
}
fn; cd; pwd

In particular, what should the effect of the assignment "home=3D/var" be?
Currently, it triggers a segmentation fault.

The global "HOME" parameter is a special parameter that determines what Zsh
considers as the home directory. The "cd" builtin, when invoked with no
arguments, sets the current directory to the home directory. Thus, "cd"
followed by "pwd" can be used to probe what Zsh considers as the home
directory.

When a local parameter is defined with the same name as an enclosing
special parameter (and no -h flag), then the local parameter inherits the
specialness of the enclosing parameter. For that reason, "typeset
HOME=3D/usr; cd; pwd" prints "/usr" (rather than "/bin"). When the local
scope is exited, the specialness is returned to the enclosing parameter.
Therefore, if "home=3D/var" is commented out, the last "pwd" prints "/bin".

The current implementation of "typeset HOME=3D/usr" moves the enclosing
parameter into the current scope (i.e., assigns "locallocal" to "pm->level"
where "pm" is the enclosing parameter's "Param" instance) and creates a
pseudo parameter in the enclosing scope that stores the state of the
enclosing parameter prior to the "typeset" (i.e., creates a "Param"
instance that duplicates most of the fields of the enclosing parameter's
"Param" instance). When the local scope is exited, the enclosing parameter
is moved back to its original scope and its state is restored to its state
prior to the "typeset" thanks to the values stored in the pseudo parameter,
which is then discarded. The pseudo parameter isn't a real parameter
because it lacks some internal attributes (in particular, the "gsu" field
of its "Param" instance is not initialized). Nevertheless, the reference
"home" currently refers to the pseudo parameter, which is why "home=3D/var"
triggers a segmentation fault.

I see two ways of properly supporting references to localized special
parameters.

One way is to consider that "typeset HOME=3D/usr" truly moves the special
parameter to the local scope until that scope is exited, at which point the
parameter is restored to its state prior to the "typeset". With that
interpretation, there only ever exists a single HOME parameter. Therefore
"home" should refer to the same variable as the global and local "HOME" and
the script should produce the following output:

/bin
/usr
/var
/bin

Another way is to consider that "typeset HOME=3D/usr" creates a local
parameter and truly moves the specialness of the enclosing parameter to the
local parameter until the local scope is exited. With that interpretation,
there are two distinct HOME parameters. The reference "home" refers to the
enclosing one. The assignment "home=3D/var" occurs at a point in time when
the enclosing parameter has lost its specialness. Therefore the script
should produce the following output:

/bin
/usr
/usr
/var

Both ways are somewhat disturbing. The former because "typeset" moves
rather than creates a parameter. The latter because it
enables temporarily striping special parameters of their specialness.

The former has the advantage that it plays well with tied parameters (=C3=
=A0 la
PATH/path). With it, there only ever exists one instance of each of the two
tied parameters, which thus can remain tied at all times. With the latter, =
one
has to consider what happens if one or the other of the two tied parameters=
 is
localized, or both, possibly in different scopes. How does tying work in
all these cases? Which parameters are tied to which ones?

I'm leaning towards the former and therefore haven't yet tried to figure
out how to handle tying with the latter. However, ksh implements the latter
but ksh doesn't have support for tied parameters.

Wdyt? Which way should Zsh adopt? Or do you see an alternative way?

Philippe

--000000000000e4e46906583dd9c0
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

<div dir=3D"ltr"><div>What should the following script print?</div><div><br=
></div><div><span style=3D"background-color:transparent"><font face=3D"mono=
space">typeset -n home=3DHOME</font></span></div><font face=3D"monospace">H=
OME=3D/bin;=C2=A0<span style=3D"background-color:transparent">cd; pwd</span=
></font><div><span style=3D"background-color:transparent"><font face=3D"mon=
ospace">function fn {</font></span></div><div><font face=3D"monospace">=C2=
=A0 typeset HOME=3D/usr;=C2=A0<span style=3D"background-color:transparent">=
cd; pwd</span></font></div><div><font face=3D"monospace">=C2=A0 home=3D/var=
;=C2=A0<span style=3D"background-color:transparent">cd; pwd</span></font></=
div><div><font face=3D"monospace">}</font></div><div><font face=3D"monospac=
e">fn;=C2=A0<span style=3D"background-color:transparent">cd; pwd</span></fo=
nt></div><div><br class=3D"gmail-Apple-interchange-newline"></div><div>In p=
articular, what should the effect of the assignment &quot;home=3D/var&quot;=
 be? Currently, it triggers a segmentation fault.</div><div><br></div><div>=
The global &quot;HOME&quot; parameter is a special parameter that determine=
s what Zsh considers as the home directory. The &quot;cd&quot; builtin, whe=
n invoked with no arguments, sets the current directory to the home directo=
ry. Thus, &quot;cd&quot; followed=C2=A0by &quot;pwd&quot; can be used to pr=
obe what Zsh considers as the home directory.</div><div><br></div><div>When=
 a local parameter is defined with the same name as an enclosing special pa=
rameter (and no -h flag), then the local parameter inherits the specialness=
 of the enclosing parameter.=C2=A0<span style=3D"background-color:transpare=
nt">For that reason, &quot;typeset HOME=3D/usr; cd; pwd&quot; prints &quot;=
/usr&quot; (rather than &quot;/bin&quot;). When the local scope is exited, =
the specialness is returned to the enclosing parameter. Therefore, if &quot=
;home=3D/var&quot; is commented out, the last &quot;pwd&quot; prints &quot;=
/bin&quot;.</span></div><div><span style=3D"background-color:transparent"><=
br></span></div><div><span style=3D"background-color:transparent">The curre=
nt implementation of &quot;</span><span style=3D"background-color:transpare=
nt">typeset HOME=3D/usr&quot; moves the enclosing parameter into the curren=
t scope (i.e., assigns &quot;locallocal&quot; to &quot;pm-&gt;level&quot; w=
here &quot;pm&quot; is the enclosing parameter&#39;s &quot;Param&quot; inst=
ance) and creates a pseudo parameter in the enclosing scope that stores the=
 state of the enclosing parameter prior to the &quot;typeset&quot; (i.e., c=
reates a &quot;Param&quot; instance that duplicates most of the fields of t=
he enclosing parameter&#39;s &quot;Param&quot; instance). When the local sc=
ope is exited, the enclosing parameter is moved back to its original scope =
and its state is restored to its state prior to the &quot;typeset&quot; tha=
nks to the values stored in the pseudo parameter, which is then discarded. =
The pseudo parameter isn&#39;t a real parameter because it lacks some inter=
nal attributes (in particular, the &quot;gsu&quot; field of its &quot;Param=
&quot; instance is not initialized). Nevertheless, the reference &quot;home=
&quot; currently refers to the pseudo parameter,=C2=A0</span><span style=3D=
"background-color:transparent">which is why &quot;home=3D/var&quot; trigger=
s a segmentation fault.</span></div><div><span style=3D"background-color:tr=
ansparent"><br></span></div><div><span style=3D"background-color:transparen=
t">I see two ways of properly supporting references to localized special pa=
rameters.</span></div><div><span style=3D"background-color:transparent"><br=
></span></div><div><span style=3D"background-color:transparent">One way is =
to consider that &quot;</span><span style=3D"background-color:transparent">=
typeset HOME=3D/usr&quot; truly moves the special parameter to the local sc=
ope until that scope is exited, at which point the parameter is restored to=
 its state prior to the &quot;typeset&quot;. With that interpretation, ther=
e only ever exists a single HOME parameter. Therefore &quot;home&quot; shou=
ld refer to the same variable as the global and local &quot;HOME&quot; and =
the script should produce the following output:</span></div><div><span styl=
e=3D"background-color:transparent"><br></span></div><div><span style=3D"bac=
kground-color:transparent"><font face=3D"monospace">/bin</font></span></div=
><div><span style=3D"background-color:transparent"><font face=3D"monospace"=
>/usr</font></span></div><div><span style=3D"background-color:transparent">=
<font face=3D"monospace">/var</font></span></div><div><span style=3D"backgr=
ound-color:transparent"><font face=3D"monospace">/bin</font></span></div><d=
iv><span style=3D"background-color:transparent"><font face=3D"monospace"><b=
r></font></span></div><div><span style=3D"background-color:transparent">Ano=
ther way is to consider that=C2=A0</span><span style=3D"background-color:tr=
ansparent">&quot;</span><span style=3D"background-color:transparent">typese=
t HOME=3D/usr&quot; creates a local parameter and truly moves the specialne=
ss of the enclosing parameter to the local parameter until the local scope =
is exited. With that interpretation, there are two distinct HOME parameters=
. The reference &quot;home&quot; refers to the enclosing one. The assignmen=
t &quot;home=3D/var&quot; occurs at a point in time when the enclosing para=
meter has lost its specialness. Therefore the script should produce the fol=
lowing output:</span></div><div><span style=3D"background-color:transparent=
"><br></span></div><div><span style=3D"background-color:transparent"><font =
face=3D"monospace">/bin</font></span></div><div><span style=3D"background-c=
olor:transparent"><font face=3D"monospace">/usr</font></span></div><div><sp=
an style=3D"background-color:transparent"><font face=3D"monospace">/usr</fo=
nt></span></div><div><span style=3D"background-color:transparent"><font fac=
e=3D"monospace">/var</font></span></div><div><span style=3D"background-colo=
r:transparent"><font face=3D"monospace"><br></font></span></div><div><span =
style=3D"background-color:transparent">Both ways are somewhat disturbing. T=
he former because &quot;typeset&quot; moves rather than creates a parameter=
. The latter because it enables=C2=A0temporarily=C2=A0striping special para=
meters of their specialness.</span></div><div><span style=3D"background-col=
or:transparent"><br></span></div><div><span style=3D"background-color:trans=
parent">The former has the advantage that it plays well with tied parameter=
s (=C3=A0 la PATH/path). With it, there only ever exists one instance of ea=
ch of the two tied parameters, which thus can remain tied at all times. Wit=
h the latter,=C2=A0</span><span style=3D"background-color:transparent">one =
has to consider what happens if one or the other=C2=A0</span>of the two tie=
d parameters=C2=A0<span style=3D"background-color:transparent">is localized=
, or both, possibly in different scopes. How does tying work in all these c=
ases? Which parameters are tied to which ones?</span></div><div><span style=
=3D"background-color:transparent"><br></span></div><div>I&#39;m leaning tow=
ards the former and therefore haven&#39;t yet tried to figure out how to ha=
ndle tying with the latter. However, ksh implements the latter but ksh does=
n&#39;t have support for tied parameters.</div><div><br></div><div>Wdyt? Wh=
ich way should Zsh adopt? Or do you see an alternative way?</div><div><br><=
/div><div>Philippe</div><div><br></div></div>

--000000000000e4e46906583dd9c0--