Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
Re: BUG: Initializations of named references with an empty string should trigger an error
- X-seq: zsh-workers 53663
- From: Philippe Altherr <philippe.altherr@xxxxxxxxx>
- To: Bart Schaefer <schaefer@xxxxxxxxxxxxxxxx>
- Cc: Zsh hackers list <zsh-workers@xxxxxxx>
- Subject: Re: BUG: Initializations of named references with an empty string should trigger an error
- Date: Thu, 22 May 2025 13:31:40 +0200
- Archived-at: <https://zsh.org/workers/53663>
- In-reply-to: <CAH+w=7YTEFH5_xB8v2S8WwGxwGzt=drqfnnSERYnpKbU-opsrw@mail.gmail.com>
- List-id: <zsh-workers.zsh.org>
- References: <CAGdYcht34aJpA6JB-ssghkRtLqAMtZRDxNvi6sCRSWaf1HNrHg@mail.gmail.com> <CAH+w=7YTEFH5_xB8v2S8WwGxwGzt=drqfnnSERYnpKbU-opsrw@mail.gmail.com>
Here is a patch that rejects empty variables but still allows to convert scalars into namerefs, even uninitialized ones:
$ typeset -n ref1=""
zsh: invalid variable name:
$ typeset ref2
$ typeset -n ref2
$ typeset -p ref2
typeset -n ref2=''
Interestingly converting a scalar initialized with an empty string triggers the invalid name error:
$ typeset ref3=""
$ typeset -n ref3
zsh: invalid variable name:
I thought that an uninitialized scalar and an empty one would be indistinguishable but that difference is actually rather welcome. It's surprising to me because I'm not aware of any other way to distinguish these two scalars.
The distinction is possible because this
ASG_VALUEP, which only checks for NULL scalars and not empty ones.
This is also a reason that namerefs shouldn't be reset to nothing when
returning from function scope.
I figured that instead of resetting the scalar I could also for example set "base" to -1 to remember that the nameref is no longer valid and that it can again be reinitialized. This way, converting the nameref to a scalar even after the referent died would still preserve the last referent name.
Philippe
On Wed, May 21, 2025 at 2:10 AM Philippe Altherr
<philippe.altherr@xxxxxxxxx> wrote:
>
> Here is what ksh does:
>
> $ f ""
> ksh: f: line 4: : invalid variable name
>
> I think that this is much better and that Zsh should do the same,
We discussed this during past development of the nameref feature. The
decision was to implement it as currently documented, for the
following reasons:
1) Long-standing zsh practice is that "typedef name" always
initializes name to a default value based on its type flags (or lack
of them). Scalars are empty string, integers are 0, etc. This also
led to the introduction of the new TYPESET_TO_UNSET option, which when
set does not initialize in this way.
2) Zsh has the ability to promote and demote scalars to/from nameref.
% typeset ref=str str=XX
% typeset -n ref
% print $ref
XX
% typeset +n ref
% print $ref
str
%
I believe ksh can do the scalar-to-nameref promotion which is where
this feature originated. Anyway, combining these features means a
nameref initialized to the empty string should not be an error.
This is also a reason that namerefs shouldn't be reset to nothing when
returning from function scope.
diff --git a/Src/params.c b/Src/params.c
index 7b515515e..e0cf45a51 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -3233,7 +3233,7 @@ assignsparam(char *s, char *val, int flags)
/* errflag |= ERRFLAG_ERROR; */
return NULL;
}
- if (*val && (v->pm->node.flags & PM_NAMEREF)) {
+ if ((v->pm->node.flags & PM_NAMEREF)) {
if (!valid_refname(val)) {
zerr("invalid variable name: %s", val);
zsfree(val);
@@ -6463,6 +6463,9 @@ upscope(Param pm, int reflevel)
mod_export int
valid_refname(char *val)
{
+ if (!*val)
+ return 0;
+
char *t = itype_end(val, INAMESPC, 0);
if (idigit(*val))
diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst
index 54f0aaf68..edbae06d9 100644
--- a/Test/K01nameref.ztst
+++ b/Test/K01nameref.ztst
@@ -141,8 +141,8 @@
typeset -n ptr=
typeset -n
-0:nameref placeholder
->ptr=''
+1:invalid empty nameref
+*?*invalid variable name:
typeset -n ptr
ptr=var
@@ -170,6 +170,17 @@
0:convert scalar to nameref
>ptr=var
+ typeset ptr
+ typeset -n ptr
+ typeset -n
+0:convert uninitialized scalar to nameref
+>ptr
+
+ typeset ptr=''
+ typeset -n ptr
+1:convert empty scalar to nameref
+*?*invalid variable name:
+
typeset -n ptr=var
typeset +n ptr
typeset -p ptr
@@ -1007,8 +1018,8 @@ F:runs in `setopt noexec` so $(...) returns nothing
>typeset ref=RW
() {
- typeset -n r1 r2=
- typeset -p r1 r2
+ typeset -n r1
+ typeset -p r1
print -- ${(!)r1-unset}
print -- ${+r1}
typeset -p r1
@@ -1016,7 +1027,6 @@ F:runs in `setopt noexec` so $(...) returns nothing
0:unset nameref remains unset when resolved
F:relies on global TYPESET_TO_UNSET in %prep
>typeset -n r1
->typeset -n r2=''
>unset
>0
>typeset -n r1
diff --git a/Test/V10private.ztst b/Test/V10private.ztst
index 26004a2dc..975145c24 100644
--- a/Test/V10private.ztst
+++ b/Test/V10private.ztst
@@ -374,7 +374,7 @@ F:Here ptr1 finds private ptr2 by scope mismatch
() {
typeset -n ptr1=ptr2
- private -n ptr2= # Assignment makes this a placeholder, not unset
+ private -n ptr2=foo
typeset -p ptr1 ptr2
typeset val=LOCAL
() {
@@ -389,12 +389,12 @@ F:Here ptr1 finds private ptr2 by scope mismatch
F:See K01nameref.ztst up-reference part 5
F:Here ptr1 finds private ptr2 by scope mismatch
>typeset -n ptr1=ptr2
->typeset -hn ptr2=''
+>typeset -hn ptr2=foo
>ptr1=ptr2
>ptr1=
>ptr2=
>typeset -n ptr1=ptr2
->typeset -hn ptr2=''
+>typeset -hn ptr2=foo
*?*ptr1: assignment failed
*?*no such variable: ptr2
Messages sorted by:
Reverse Date,
Date,
Thread,
Author