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

Up-scope named references, vs. ksh



Re-establishing the original examples:

On Sat, Feb 10, 2024 at 11:00 PM Stephane Chazelas
<stephane@xxxxxxxxxxxx> wrote:
>
> $ ./Src/zsh -c 'function f { typeset -n ref=$1; typeset var=foo; ref=X; echo "$ref ${(!)ref} $var"; }; f var; echo "$var"'
> X var X
>
> Compare with ksh:
>
> $ ksh -c 'function f { typeset -n ref=$1; typeset var=foo; ref=X; echo "$ref ${!ref} $var"; }; f var; echo "$var"'
> X var foo
> X

On Sun, Feb 11, 2024 at 8:14 AM Bart Schaefer <schaefer@xxxxxxxxxxxxxxxx> wrote:
>
> On Sun, Feb 11, 2024 at 1:00 AM Stephane Chazelas <stephane@xxxxxxxxxxxx> wrote:
> >
> > One difference with ksh93: if the target variable was not set at
> > the time of the "typeset -n ref=target", then when ref is
> > assigned it may end up refering to a local target
[...]
> > It can be worked around with a typeset -g $1 before the typeset -n var=$1
>
> Is that really sufficient? Might the -g not select the wrong scope if
> the string $1 was passed down a couple of layers?

Here's what I'm concerned about:

 A) Src/zsh -c 'function f { typeset -n ref; ref=$1; typeset var=foo;
ref=X; echo "$ref ${(!)ref} $var"; }; f var; echo "$var"'

Note the subtle difference of separating the declaration of "ref" from
assignment to it.  Then consider this variation:

 B) Src/zsh -c 'function f { typeset -n ref; typeset var=foo; ref=$1;
ref=X; echo "$ref ${(!)ref} $var"; }; f var; echo "$var"'

Now the assignment is after the declaration of the local. And finally:

C) Src/zsh -c 'function f { typeset var=foo; typeset -n ref=$1;
ref=X; echo "$ref ${(!)ref} $var"; }; f var; echo "$var"'

Now the declaration+assignment of the nameref is after the local.

At least for the versions I have installed on Ubuntu 20.04, and with
parens removed from (!) as appropriate,
 A) ksh prints "X var foo\nX"; mksh prints an error and does not
create a nameref; Src/zsh prints "X var X".
 B) ksh prints "X var X"; mksh prints an error and does not create a
nameref; Src/zsh prints "X var X".
 C) ksh prints "X var foo\nX"; mksh prints "X var X"; Src/zsh prints "X var X".

So in part C, ksh "knows" that $1 is not just a string but an object
that belongs to a surrounding scope.  That's not going to happen in
zsh for a long time, if ever.  It's interesting that it matters that
the assignment appears in the argument of "typeset" e.g. part C vs.
part B.

Incidentally if I change the delcaration to
 typeset -n ref=
then ksh crashes in parts A and B.  The behavior of mksh and Src/zsh
is unchanged.

Adding the "typeset -g $1" changes the outcome for zsh only for part
A, where it prints "X var foo\nX" as Stephane indicated.  (The other
shells don't support -g.)

Given that it's not possible to fix part C for zsh, and zsh agrees
with ksh on part B and with mksh on B and C, is it worth making an
effort to fix Stephane's original example along with part A ?

> Seems to me you
> have to avoid re-using a variable regardless, especially if you do
>
> typeset -n var=
>
> (create what I've been calling a "placeholder") and then assign to var
> in some nested scope.

I have not dived into what happens in ksh if the assignments in A and
B happen in a nested function call.

> This may particularly be true of the "for var in ..." special case for namerefs.

The potential for confusion here seems large.  If I do

 typeset -n up=$1
 typeset var=foo
 for up in $2 $3 $4 $5; do ...

what scope am I applying to each of the words in $2, $3, ... ?  (E.g.,
suppose $3 is "var" instead of $1.)  Does it change if I do

 typeset -n up

instead?  Should that act like part B where the declaration is
separate from the assignment?  What if I do

 for up in "$@"

there?  The ksh implementation seems both inconsistent and slightly magical.

Anyway, the way to "fix" this is to implicitly perform the "typeset
-g" when assigning to the nameref, and if that creates a new parameter
then mark it unset.  But that still may put the new parameter at
global scope rather than at the scope from which $1 was passed; the
expansion of $1 is just a string like any other scalar, and there's no
magic to change that.  It seems as though the more consistent behavior
is what zsh is already doing.  I'm unsure whether doing the implicit
typeset only in ksh emulation mode is worthwhile as it gets us only
partway there.

Adding the "typeset -g" changes the behavior of a large number of
tests in K01, too.

Thoughts?




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