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

Re: nameref binding not finding calling-scope vars?



On Sat, Mar 29, 2025 at 12:19 AM Phil Pennock
<zsh-workers+phil.pennock@xxxxxxxxxxxx> wrote:
>
> Say we want a function to set a variable whose name is given to it, so
> we use a nameref.  We want to set the variable to be an array, even if
> outside it exists as a non-array.

This is tricky even ignoring named references.  One can't actually
change the type of a parameter in zsh except in some limited cases of
changing output format flags.  Anything that looks like a type change
is actually an implicit unset followed by re-creating the parameter.

This causes several problems for named references if you try to change
something at a different local level, because each parameter by name
has its own chain of scopes, so affecting anything other than the
deepest object in the chain is problematic.

> % inner() { local -n var="${1:?}"; typeset -ga var=(alpha beta gamma); }
> % outer() { local -a foo=(); inner foo; typeset -p foo }
> % foo=3
> % typeset -p foo; outer foo; typeset -p foo
> typeset foo=3
> typeset -g -a foo=( alpha beta gamma )
> typeset -a foo=( alpha beta gamma )
>
> This seems wrong.  `outer()` declared foo to be local, so inner should
> not have been able to reset it back to being at global scope, that final
> line should have shown foo=3, AIUI.

What's happening here is that the var-to-foo transition happens first,
and then the -g flag moves up the chain from that point and alters the
global.  This is in fact a bug, because it leaks the structure for the
local declared in outer.  This should at minimum have produced an
assignment error.

> % inner() { local -n var="${1:?}"; var=(alpha beta gamma); }
> % foo=3
> % typeset -p foo; outer foo; typeset -p foo
> typeset foo=3
> inner: foo: attempt to assign array value to non-array

This one I would have expected to work ... but it appears the same
thing is happening here as in the typeset -g case, except this time it
actually does catch the error.

> So using a nameref seems to ignore up-callstack local variables?

It's assignment that has the problem, not nameref per se.  If you're
not assigning through the reference everything works.

> The docs have been improved since before I rebuilt; so I'd been reading
> the docs as of commit a528af5c57 (2023-12-16) and now has discussion of
> `-u` to find in the parent scope.

That skips over a declaration of the referent name in the local scope,
it shouldn't matter in your example because "inner" does not declare
"local foo".

The following should fix your example, but it's still possible to
trigger the aforementioned bug by tweaking the declarations and
assignments, so I'll see about sending a more formal patch later.  It
will NOT allow you to change the type of the referent by direct
assignment.  So how do you fix that?  You explicitly unset it rather
than rely on the implicit unset.

  inner() { local -n var="${1:?}"; unset var; var=(alpha beta gamma); }

diff --git a/Src/params.c b/Src/params.c
index d1c06b893..bb0b926dd 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -6395,7 +6395,9 @@ setscope(Param pm)
                }
            } else
                pm->base = basepm->level;
-       }
+       } else if (pm->base < locallevel && refname &&
+                  (basepm = (Param)getparamnode(realparamtab, refname)))
+           pm->base = basepm->level;
        if (pm->base > pm->level) {
            if (EMULATION(EMULATE_KSH)) {
                zerr("%s: global reference cannot refer to local variable",




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