Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
[PATCH] "typeset -nu" (was Re: Up-scope named references, vs. ksh)
- X-seq: zsh-workers 52650
- From: Bart Schaefer <schaefer@xxxxxxxxxxxxxxxx>
- To: Zsh hackers list <zsh-workers@xxxxxxx>
- Subject: [PATCH] "typeset -nu" (was Re: Up-scope named references, vs. ksh)
- Date: Sat, 2 Mar 2024 15:55:23 -0800
- Archived-at: <https://zsh.org/workers/52650>
- In-reply-to: <CAH+w=7aAHb5qc2ZmjgKPKw0vX4sFRpPhdKhYtfMuFGQK0L2qxA@mail.gmail.com>
- List-id: <zsh-workers.zsh.org>
- References: <CAH+w=7YVJO-HkneMpnfBbqBztPaXdXTD=mo-vHbdUW00TiFVBQ@mail.gmail.com> <40726-1676098925.110777@U2kb.d0Pd.I9ml> <CAH+w=7aKwKhdXjPo2FQG1GqjHQzX=5to_m6kZeL-UFfQh_XMtw@mail.gmail.com> <20240211070042.4j37hkgjjn3dfjqd@chazelas.org> <CAH+w=7ateaqX5azdifTaFpJT6sX-fVhnEazgeYYXSWtJY8EQTw@mail.gmail.com> <CAH+w=7akb334QvsofyMLtc7_091bmP=omjAGOZSc8eH8FWuTqQ@mail.gmail.com> <20240220210553.g6imt3op6zahz4pa@chazelas.org> <CAH+w=7bucofSovKLc0kJeMT3RcKUc8ydhFSaGQqMALvJ_0S21Q@mail.gmail.com> <20240221201215.anpjcfav6na55gg6@chazelas.org> <CAH+w=7aGtWkXFYXQRnL808CscE0=CsAvA3zpoUK6LcSzk5JEww@mail.gmail.com> <20240301182238.tpyajwblbam5bxw7@chazelas.org> <CAH+w=7YAgtUDrrgrDedVpq478ow7S=Renj+rjs0ZF6ZFTqP5OQ@mail.gmail.com> <CAH+w=7aAHb5qc2ZmjgKPKw0vX4sFRpPhdKhYtfMuFGQK0L2qxA@mail.gmail.com>
On Fri, Mar 1, 2024 at 11:29 PM Bart Schaefer <schaefer@xxxxxxxxxxxxxxxx> wrote:
>
> How about this: Instead of magically turning a positional into a
> reference into the surrounding scope, let's make it explicit.
Here's a patch to implement this, including doc with an example.
Of particular note is the last sentence here:
+To force a named reference to refer to the outer scope, even if a local
+has already been declared, add the tt(-u) option when declaring the
+named reference. In this case var(rname) should already exist in the
+outer scope, otherwise the behavior of assignment through var(pname)
+is not defined and may change the scope of the reference or fail with
+a status of 1.
I went with assignment silently failing rather than printing an error
message in the cases where a parameter in the calling scope cannot be
created.
diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo
index 6318053d8..7a9684ac8 100644
--- a/Doc/Zsh/builtins.yo
+++ b/Doc/Zsh/builtins.yo
@@ -2052,13 +2052,17 @@ cindex(named reference)
cindex(reference, named)
The flag tt(-n) creates a em(named reference) to another parameter.
The second parameter need not exist at the time the reference is
-created. Only tt(-g) and tt(-r) may be used in conjunction with
+created. Only tt(-g), tt(-u), and tt(-r) may be used in conjunction with
tt(-n). The var(name) so created may not be an array element nor use
a subscript, but the var(value) assigned may be any valid parameter
name syntax, even a subscripted array element (including an associative
array element) or an array slice, which is evaluated when the named
reference is expanded. It is an error for a named reference to refer
-to itself, even indirectly through a chain of references.
+to itself, even indirectly through a chain of references. When tt(-u)
+is applied to a named reference, the parameter identified by var(value)
+is always found in the calling function scope rather than the current
+local scope. In this case, if there is no such parameter in the calling
+scope, assignments to the named reference may fail, setting tt($?) to 1.
See ifzman(zmanref(zshexpn))ifnzman(noderef(Parameter Expansion)) and
ifzman(zmanref(zshparam))ifnzman(noderef(Parameters)) for details of the
behavior of named references.
diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo
index 2acfd08c9..183ca6e03 100644
--- a/Doc/Zsh/expn.yo
+++ b/Doc/Zsh/expn.yo
@@ -1600,6 +1600,25 @@ example(tt(before local: OUTER)
tt(by reference: OUTER)
tt(after func: RESULT))
+To force a named reference to refer to the outer scope, even if a local
+has already been declared, add the tt(-u) option when declaring the
+named reference. In this case var(rname) should already exist in the
+outer scope, otherwise the behavior of assignment through var(pname)
+is not defined and may change the scope of the reference or fail with
+a status of 1. Example of correct usage:
+ifzman()
+example(tt(caller=OUTER)
+tt(func LPAR()RPAR() {)
+tt( print before local: $caller)
+tt( local caller=INNER)
+tt( print after local: $caller)
+tt( typeset -n -u outer=$1)
+tt( print by reference: $outer)
+tt( outer=RESULT)
+tt(})
+tt(func caller)
+tt(print after func: $caller))
+
Note, however, that named references to em(special) parameters acquire
the behavior of the special parameter, regardless of the scope where
the reference is declared.
diff --git a/Doc/Zsh/func.yo b/Doc/Zsh/func.yo
index d4914df7a..7b71e34e9 100644
--- a/Doc/Zsh/func.yo
+++ b/Doc/Zsh/func.yo
@@ -31,10 +31,12 @@ referent parameter is in scope, and as early as possible in the
function if the reference is to a parameter in a calling scope.
A typical use of named references is to pass the name
-of the referent as a positional parameter. For example,
+of the referent as a positional parameter. In this case it is
+good practice to use the tt(-u) option to reference the calling
+scope. For example,
ifzman()
example(pop+LPAR()RPAR() {
- local -n ref=$1
+ local -nu ref=$1
local last=$ref[$#ref]
ref[$#ref]=LPAR()RPAR()
print -r -- $last
@@ -43,9 +45,10 @@ array=LPAR() a list of five values RPAR()
pop array)
prints the word `tt(values)' and shortens `tt($array)' to
-`tt(LPAR() a list of five RPAR())'. There are no local parameters in
-tt(pop) at the time `tt(ref=$1)' is assigned, so `tt(ref)' becomes a
-reference to `tt(array)' in the caller.
+`tt(LPAR() a list of five RPAR())'. With tt(-nu), `tt(ref)' becomes a
+reference to `tt(array)' in the caller. There are no local parameters in
+tt(pop) at the time `tt(ref=$1)' is assigned, so in this example tt(-u)
+could have been omitted, but it makes the intention clear.
Functions execute in the same process as the caller and
share all files
diff --git a/Doc/Zsh/mod_ksh93.yo b/Doc/Zsh/mod_ksh93.yo
index 9cd708d10..7508758aa 100644
--- a/Doc/Zsh/mod_ksh93.yo
+++ b/Doc/Zsh/mod_ksh93.yo
@@ -12,7 +12,7 @@ The single builtin provided by this module is:
startitem()
findex(nameref)
cindex(named references, creating)
-item(tt(nameref) [ tt(-r) ] var(pname)[tt(=)var(rname)])(
+item(tt(nameref) [ tt(-gur) ] var(pname)[tt(=)var(rname)])(
Equivalent to tt(typeset -n )var(pname)tt(=)var(rname)
However, tt(nameref) is a builtin command rather than a reserved word,
diff --git a/Etc/FAQ.yo b/Etc/FAQ.yo
index 145ef02c9..4a86050e6 100644
--- a/Etc/FAQ.yo
+++ b/Etc/FAQ.yo
@@ -1025,6 +1025,13 @@ label(210)
HIT:SPOT
)
+ Dynamic scoping applies to named references, so for example a named
+ reference declared in global scope may be used in function scopes.
+ In ksh, local parameters have static scope, so named references in
+ zsh may have side-effects that do not occur in ksh. To limit those
+ effects, mytt(zmodload zsh/param/private) and declare all named
+ references mytt(private).
+
Named references may be used in zsh versions later than 5.9.
sect(What is zsh's support for non-forking command substitution?)
diff --git a/Src/Modules/ksh93.c b/Src/Modules/ksh93.c
index 9af5e1d69..6760cbca0 100644
--- a/Src/Modules/ksh93.c
+++ b/Src/Modules/ksh93.c
@@ -38,7 +38,7 @@
*/
static struct builtin bintab[] = {
- BUILTIN("nameref", BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "gr", "n")
+ BUILTIN("nameref", BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "gur", "n")
};
#include "zsh.mdh"
diff --git a/Src/builtin.c b/Src/builtin.c
index 83144677b..ba9cb03c2 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -2699,7 +2699,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
off |= bit;
}
if (OPT_MINUS(ops,'n')) {
- if ((on|off) & ~PM_READONLY) {
+ if ((on|off) & ~(PM_READONLY|PM_UPPER)) {
zwarnnam(name, "no other attributes allowed with -n");
return 1;
}
diff --git a/Src/exec.c b/Src/exec.c
index d85adbea5..8be7bf17d 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -1322,7 +1322,7 @@ execsimple(Estate state)
fputc('\n', xtrerr);
fflush(xtrerr);
}
- lv = (errflag ? errflag : cmdoutval);
+ lv = (errflag ? errflag : cmdoutval ? cmdoutval : lastval);
} else {
int q = queue_signal_level();
dont_queue_signals();
@@ -2604,6 +2604,7 @@ addvars(Estate state, Wordcode pc, int addflags)
opts[ALLEXPORT] = allexp;
} else
pm = assignsparam(name, val, myflags);
+ lastval = !pm; /* zerr("%s: assignment failed", name); */
if (errflag) {
state->pc = opc;
return;
@@ -2628,7 +2629,9 @@ addvars(Estate state, Wordcode pc, int addflags)
}
fprintf(xtrerr, ") ");
}
- assignaparam(name, arr, myflags);
+ ;
+ lastval = !assignaparam(name, arr, myflags);
+ /* zerr("%s: array assignment failed", name); */
if (errflag) {
state->pc = opc;
return;
diff --git a/Src/params.c b/Src/params.c
index 064dbd2bc..790c978f2 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -1060,8 +1060,7 @@ createparam(char *name, int flags)
"BUG: local parameter is not unset");
oldpm = lastpm;
}
- } else
- flags |= PM_NAMEREF;
+ }
}
DPUTS(oldpm && oldpm->level > locallevel,
@@ -6264,10 +6263,12 @@ resolve_nameref(Param pm, const Asgment stop)
}
} else if ((hn = gethashnode2(realparamtab, seek))) {
if (pm) {
- if (!(stop && (stop->flags & (PM_LOCAL))))
- hn = (HashNode)upscope((Param)hn,
- ((pm->node.flags & PM_NAMEREF) ?
- pm->base : ((Param)hn)->level));
+ if (!(stop && (stop->flags & (PM_LOCAL)))) {
+ int scope = ((pm->node.flags & PM_NAMEREF) ?
+ ((pm->node.flags & PM_UPPER) ? -1 :
+ pm->base) : ((Param)hn)->level);
+ hn = (HashNode)upscope((Param)hn, scope);
+ }
/* user can't tag a nameref, safe for loop detection */
pm->node.flags |= PM_TAGGED;
}
@@ -6313,11 +6314,13 @@ setloopvar(char *name, char *value)
static void
setscope(Param pm)
{
- if (pm->node.flags & PM_NAMEREF) {
+ queue_signals();
+ if (pm->node.flags & PM_NAMEREF) do {
Param basepm;
struct asgment stop;
char *refname = GETREFNAME(pm);
char *t = refname ? itype_end(refname, INAMESPC, 0) : NULL;
+ int q = queue_signal_level();
/* Temporarily change nameref to array parameter itself */
if (t && *t == '[')
@@ -6327,9 +6330,11 @@ setscope(Param pm)
stop.name = "";
stop.value.scalar = NULL;
stop.flags = PM_NAMEREF;
- if (locallevel)
+ if (locallevel && !(pm->node.flags & PM_UPPER))
stop.flags |= PM_LOCAL;
+ dont_queue_signals(); /* Prevent unkillable loops */
basepm = (Param)resolve_nameref(pm, &stop);
+ restore_queue_signals(q);
if (t) {
pm->width = t - refname;
*t = '[';
@@ -6342,7 +6347,7 @@ setscope(Param pm)
if (upscope(pm, pm->base) == pm) {
zerr("%s: invalid self reference", refname);
unsetparam_pm(pm, 0, 1);
- return;
+ break;
}
pm->node.flags &= ~PM_SELFREF;
} else if (pm->base == pm->level) {
@@ -6350,7 +6355,7 @@ setscope(Param pm)
strcmp(pm->node.nam, refname) == 0) {
zerr("%s: invalid self reference", refname);
unsetparam_pm(pm, 0, 1);
- return;
+ break;
}
}
} else if ((t = GETREFNAME(basepm))) {
@@ -6358,7 +6363,7 @@ setscope(Param pm)
strcmp(pm->node.nam, t) == 0) {
zerr("%s: invalid self reference", refname);
unsetparam_pm(pm, 0, 1);
- return;
+ break;
}
}
} else
@@ -6378,7 +6383,8 @@ setscope(Param pm)
zerr("%s: invalid self reference", refname);
unsetparam_pm(pm, 0, 1);
}
- }
+ } while (0);
+ unqueue_signals();
}
/**/
@@ -6390,6 +6396,8 @@ upscope(Param pm, int reflevel)
pm = up;
up = up->old;
}
+ if (reflevel < 0 && locallevel > 0)
+ return pm->level == locallevel ? up : pm;
return pm;
}
diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst
index ff48e2289..6db819389 100644
--- a/Test/K01nameref.ztst
+++ b/Test/K01nameref.ztst
@@ -492,7 +492,6 @@ F:unexpected side-effects of previous tests
}
typeset -p ptr2
1:up-reference part 5, stacked namerefs, end not in scope
-F:What is the correct behavior for the scope of ptr1?
>typeset -n ptr1=ptr2
>typeset -n ptr2
>ptr1=ptr2
@@ -529,6 +528,49 @@ F:Same test, should part 5 output look like this?
>typeset -n ptr2
>typeset ptr2=val
+ () {
+ () {
+ local var
+ typeset -nu ptr1=var
+ ptr1=outer && print -u2 assignment expected to fail
+ typeset -n ptr2=var
+ ptr2=inner
+ typeset -n
+ printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2"
+ }
+ typeset -p var
+ }
+ typeset -p var
+1:up-reference part 7, upscope namerefs, end not in scope
+>ptr1=var
+>ptr2=var
+>ptr1=
+>ptr2=inner
+*?*typeset*: no such variable: var
+?*typeset*: no such variable: var
+
+ typeset var
+ () {
+ () {
+ local var
+ typeset -nu ptr1=var
+ ptr1=outer || print -u2 assignment expected to succeed
+ typeset -n ptr2=var
+ ptr2=inner
+ typeset -n
+ printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2"
+ }
+ typeset -p var
+ }
+ typeset -p var
+0:up-reference part 8, upscope namerefs, end in scope
+>ptr1=var
+>ptr2=var
+>ptr1=outer
+>ptr2=inner
+>typeset -g var=outer
+>typeset var=outer
+
if zmodload zsh/parameter; then
() {
zmodload -u zsh/parameter
@@ -539,7 +581,7 @@ F:Same test, should part 5 output look like this?
}
else ZTST_skip='Cannot zmodload zsh/parameter, skipping autoload test'
fi
-0:up-reference part 3, autoloading with hidden special
+0:up-reference part 9, autoloading with hidden special
>nameref-local-nameref-local
>typeset -h parameters
@@ -762,12 +804,17 @@ F:relies on global TYPESET_TO_UNSET in %prep
bar=xx
typeset -n foo=bar
- () { typeset -n foo; foo=zz; foo=zz; print $bar $zz }
+ () {
+ typeset -n foo; foo=zz
+ foo=zz || print -u2 foo: assignment failed
+ print $bar $zz
+ }
() { typeset -n foo; foo=zz; local zz; foo=zz; print $bar $zz }
0:regression: local nameref may not in-scope a global parameter
F:previously this could create an infinite recursion and crash
>xx
>xx zz
+*?*foo: assignment failed
typeset -nm foo=bar
1:create nameref by pattern match not allowed
diff --git a/Test/V10private.ztst b/Test/V10private.ztst
index efa346002..ed51316f3 100644
--- a/Test/V10private.ztst
+++ b/Test/V10private.ztst
@@ -378,7 +378,7 @@ F:Here ptr1 finds private ptr2 by scope mismatch
typeset -p ptr1 ptr2
typeset val=LOCAL
() {
- ptr1=val # This is a silent no-op, why?
+ ptr1=val || print -u2 ptr1: assignment failed
typeset -n
printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2"
}
@@ -388,7 +388,6 @@ F:Here ptr1 finds private ptr2 by scope mismatch
1:up-reference for private namerefs, end not in scope
F:See K01nameref.ztst up-reference part 5
F:Here ptr1 finds private ptr2 by scope mismatch
-F:Assignment silently fails, is that correct?
>typeset -n ptr1=ptr2
>typeset -hn ptr2=''
>ptr1=ptr2
@@ -396,6 +395,7 @@ F:Assignment silently fails, is that correct?
>ptr2=
>typeset -n ptr1=ptr2
>typeset -hn ptr2=''
+*?*ptr1: assignment failed
*?*no such variable: ptr2
typeset ptr2
Messages sorted by:
Reverse Date,
Date,
Thread,
Author