Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: A solution to fix hidden references in reference chains



I don't recall all the details (again, other readers?) but my
recollection is that there's a POSIXy issue here.  E.g., an exported
parameter remains exported even if unset (so re-assigning it updates
the environment), and I believe numeric types are supposed to retain
their properties across unset.
 This is why I wonder whether
references ought also to retain reference-ness across unset.

I always considered it a bug that references remain references across unset because in Zsh only local references exhibit this behavior (not global ones) and no other type (integer, float, array, ...) is retained across unset. I have now confirmed that no type is retained across unset neither in ksh, nor in bash. See details below. Given all that, I think that workers/54236 is correct.

Regarding exported parameters, references can't be exported; "typeset -n -x ref=var" raises an error. Therefore, I don't think we need to look at what happens with exported parameters. That being said, even with exported parameters I couldn't find a case where a type is retained across unset.

Below are functions and their output that I have used to confirm that no types are retained across unset. In each output, I have highlighted in red the result of "typeset -p ..." after the parameters were resurrected.

Zsh

The following function allows to test all parameter types for local (zsh-test-unset), global (G=-g zsh-test-unset), exported global (G=-g X=-x zsh-test-unset) and exported local (G=+g X=-x zsh-test-unset) parameters:

zsh-test-unset() {
  typeset $G $X    s=foo
  typeset $G $X -i i=42
  typeset $G $X -E E=4.2
  typeset $G $X -F F=4.2
  typeset $G $X -a a=(foo1 foo2)
  typeset $G $X -A A=([fooK]=fooV)
  typeset $G    -n n=foo

  PS4="# "; set -x
  typeset -p s i E F a A n
  unset   -n s i E F a A n
  typeset -p s i E F a A n
  typeset $G s i E F a A n
  typeset -p s i E F a A n
}


Here is the output for local parameters:

$ zsh-test-unset
# typeset -p s i E F a A n
# typeset -p s i E F a A n
typeset s=foo
typeset -i i=42
typeset -E E=4.200000000e+00
typeset -F F=4.2000000000
typeset -a a=( foo1 foo2 )
typeset -A A=( [fooK]=fooV )
typeset -n n=foo
# unset -n s i E F a A n
# typeset -p s i E F a A n
# typeset s i E F a A n
n=''
# typeset -p s i E F a A n
typeset s=''
typeset i=''
typeset E=''
typeset F=''
typeset a=''
typeset A=''
typeset -n n=''

Only the reference type is retained across unset.

Ksh (Version AJM 93u+m/1.0.10 2024-08-01)

Here is a similar function for ksh. Note that with ksh, "typeset var" doesn't create/resurrect any parameter; a type and/or a value has to be provided to trigger the creation/resurrection of a parameter.

function ksh_test_unset {
  typeset $G $X    s=foo
  typeset $G $X -i i=42
  typeset $G $X -E E=4.2
  typeset $G $X -F F=4.2
  typeset $G $X -a a=(foo1 foo2)
  typeset $G $X -A A=([fooK]=fooV)
  typeset $G    -n n=foo

  PS4="# "; set -x
  typeset -p s i E F a A n
  unset   -n s i E F a A n
  typeset -p s i E F a A n
  typeset $G s i E F a A n
  typeset -p s i E F a A n
  typeset $G s=bar i=bar E=bar F=bar a=bar A=bar n=bar
  typeset -p s i E F a A n
}


Here is the output for local parameters:

$ ksh_test_unset
# typeset -p s i E F a A n
s=foo
typeset -i i=42
typeset -E E=4.2
typeset -F F=4.2000000000
typeset -a a=(foo1 foo2)
typeset -A A=([fooK]=fooV)
typeset -n n=foo
# unset -n s i E F a A n
# typeset -p s i E F a A n
# typeset s i E F a A n
# typeset -p s i E F a A n
# s=bar
# i=bar
# E=bar
# F=bar
# a=bar
# A=bar
# n=bar
# typeset s i E F a A n
# typeset -p s i E F a A n
s=bar
i=bar
E=bar
F=bar
a=bar
A=bar
n=bar


No type is retained across unset.

Bash (5.2.21(1)-release)

Here is the function for bash, which doesn't have support for float parameters:

bash-test-unset() {
  typeset $G $X    s=foo
  typeset $G $X -i i=42
  typeset $G $X -a a=(foo1 foo2)
  typeset $G $X -A A=([fooK]=fooV)
  typeset $G $X -n n=foo

  PS4="# "; set -x
  typeset -p s i a A n
  unset      s i a A
  unset   -n         n
  typeset -p s i a A n
  typeset $G s i a A n
  typeset -p s i a A n
}


Here is the output for local parameters:

$ bash-test-unset
# typeset -p s i a A n
declare -- s="foo"
declare -i i="42"
declare -a a=([0]="foo1" [1]="foo2")
declare -A A=([fooK]="fooV" )
declare -n n="foo"
# unset s i a A
# unset -n n
# typeset -p s i a A n
declare -- s
declare -- i
declare -- a
declare -- A
declare -- n
# typeset s i a A n
# typeset -p s i a A n
declare -- s
declare -- i
declare -- a
declare -- A
declare -- n


No type is retained across unset.

Philippe
 

On Mon, Mar 23, 2026 at 3:21 AM Bart Schaefer <schaefer@xxxxxxxxxxxxxxxx> wrote:
On Sun, Mar 22, 2026 at 6:15 PM Philippe Altherr
<philippe.altherr@xxxxxxxxx> wrote:
>>
>> But you can't change a named reference into something else that way.
>> Even "unset -n" doesn't remove the nameref-ness of the surrounding
>> scope parameter.
>
> Yep but I was working on a fix for this issue. I have now sent the patch, see workers/54236.

This is exactly the thing I meant was a conflict.  I'm not convinced
workers/54236 is correct in this regard.

>> > - Ref added to a scope list is now an integer with base=N
>>
>> Per above, I think this is actually impossible, at least at present?
>
> Once workers/54236 is committed all of that should become possible.

Again, should it?  (Asking others, not Philippe.)  Related:

> - In workers/54236, I had to add many checks for PM_UNSET and PM_DECLARED. Most of these would not be needed if whenever a local variable is unset all its flags were cleared and replaced with PM_UNSET. This would guarantee that if a parameter has a type flag (one of PM_ARRAY, PM_INTEGER, PM_NAMEREF, ...) then it's for sure a still alive parameter of that type. Currently, you should always check for the presence of the type flag and the absence of PM_UNSET or the presence of PM_DECLARED, which is rather verbose and very error prone. Do you see any reason why flags should NOT be cleared when a parameter is unset? Afaik, there exists no mechanism that allows undeleting an unset parameter. So I don't see any reason why flags would need to be kept after a parameter is unset.

I don't recall all the details (again, other readers?) but my
recollection is that there's a POSIXy issue here.  E.g., an exported
parameter remains exported even if unset (so re-assigning it updates
the environment), and I believe numeric types are supposed to retain
their properties across unset.  This is why I wonder whether
references ought also to retain reference-ness across unset.

> - I wonder whether we would be better served if PM_DECLARED was replaced with a PM_NULL

We had that argument at some length before PM_DECLARED was introduced,
and decided against PM_NULL, and I'm not excited about rehashing the
topic.

> - Bart, you once suggested that dereferencing not-yet-initialized references ought to trigger an error. Currently such references need to be handled in several places and, when they are part of assignments, they trigger various kinds of errors/warnings that may not necessarily make much sense to end users. My impression is that things could be simpler and more uniform if dereferencing a not-yet-initialized reference would always trigger an error. I will try to write a patch that does that,

There, we need to consider what happens with ksh.


Messages sorted by: Reverse Date, Date, Thread, Author