Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
Re: BUG: Assigning a string to an array via a reference fails to change its type
- X-seq: zsh-workers 54057
- From: Philippe Altherr <philippe.altherr@xxxxxxxxx>
- To: Bart Schaefer <schaefer@xxxxxxxxxxxxxxxx>
- Cc: Oliver Kiddle <opk@xxxxxxx>, Zsh hackers list <zsh-workers@xxxxxxx>
- Subject: Re: BUG: Assigning a string to an array via a reference fails to change its type
- Date: Sun, 9 Nov 2025 03:12:41 +0100
- Archived-at: <https://zsh.org/workers/54057>
- In-reply-to: <CAH+w=7a-LPid3BoegNSTHyHK7XkVnTWpCrrb3EgHgo30fnbTvg@mail.gmail.com>
- List-id: <zsh-workers.zsh.org>
- References: <CAGdYchvQ_vdN74r6SSYsUehgLpcf2+N4Bzh0AvGX3v-780stsA@mail.gmail.com> <CAHYJk3Rsb3cRWHEjZhHzVax1ZN8p+x+NA4-HKjm2dyt48X=CjA@mail.gmail.com> <CAGdYcht=OSTS_3Dro+0WdRXNaraJurbEeEPnewYZFhJVS_f6Kg@mail.gmail.com> <CAH+w=7a=M6HbsC1=xhzzqX2xLjFCfPf7ECkXSwn-HqjUBEpQtQ@mail.gmail.com> <CAGdYchu7_ryJS+wuUm5nnz=VvyWUofMJgH43dSP6oNmB6NVJMw@mail.gmail.com> <CAGdYcht5HMnSV10eu8wmsHTJAd6503oh0qU+__Ck3cgS8BZf+Q@mail.gmail.com> <36410-1762630805.672502@VUbU.Dy6d.9HaB> <CAH+w=7a-LPid3BoegNSTHyHK7XkVnTWpCrrb3EgHgo30fnbTvg@mail.gmail.com>
Here is a version that uses a new flag instead of a new field. I also reordered the fields of the "value" struct for better field alignments.
What about the flags field? Your isrefslice is logically just a boolean,
right? So it could be another VALFLAG item in the enum. There's not so
many places in the code to check to see whether instances of v.flags = 0
actually need to only mask off the other three bits or equivalents for
other assignments.
I checked all the v.flags = 0. They are all initializations that can remain so.
Philippe
On Sat, Nov 8, 2025, 11:40 AM Oliver Kiddle <
opk@xxxxxxx> wrote:
Philippe Altherr wrote:
> The patch adds a new field "isrefslice" to the "value" struct.
What about the flags field?
Exactly what I was about to say.
diff --git a/Src/params.c b/Src/params.c
index 5a0434e40..b76fb8a6b 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -2203,6 +2203,7 @@ fetchvalue(Value v, char **pptr, int bracks, int flags)
} else {
Param pm;
int isvarat;
+ int isrefslice = 0;
isvarat = (t[0] == '@' && !t[1]);
if (flags & SCANPM_NONAMEREF)
@@ -2250,6 +2251,7 @@ fetchvalue(Value v, char **pptr, int bracks, int flags)
flags |= SCANPM_NOEXEC;
*ss = sav;
s = dyncat(ss,*pptr);
+ isrefslice = 1;
} else
s = *pptr;
}
@@ -2264,7 +2266,7 @@ fetchvalue(Value v, char **pptr, int bracks, int flags)
v->isarr = SCANPM_ARRONLY;
}
v->pm = pm;
- v->flags = 0;
+ v->flags = isrefslice ? VALFLAG_REFSLICE : 0;
v->start = 0;
v->end = -1;
if (bracks > 0 && (*s == '[' || *s == Inbrack)) {
@@ -3222,12 +3224,18 @@ assignsparam(char *s, char *val, int flags)
if (!(v = getvalue(&vbuf, &s, 1))) {
createparam(t, PM_SCALAR);
created = 1;
- } else if ((((v->pm->node.flags & PM_ARRAY) && !(flags & ASSPM_AUGMENT)) ||
- (v->pm->node.flags & PM_HASHED)) &&
- !(v->pm->node.flags & (PM_SPECIAL|PM_TIED)) &&
- unset(KSHARRAYS)) {
- unsetparam(t);
- createparam(t, PM_SCALAR);
+ } else if ((((v->pm->node.flags & PM_ARRAY) &&
+ !(v->flags & VALFLAG_REFSLICE) &&
+ !(flags & ASSPM_AUGMENT)) ||
+ (v->pm->node.flags & PM_HASHED)) &&
+ !(v->pm->node.flags & (PM_SPECIAL|PM_TIED)) &&
+ unset(KSHARRAYS)) {
+ if (resetparam(v->pm, PM_SCALAR)) {
+ unqueue_signals();
+ zsfree(val);
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
/* not regarded as a new creation */
v = NULL;
}
@@ -3378,7 +3386,8 @@ assignaparam(char *s, char **val, int flags)
createparam(t, PM_ARRAY);
created = 1;
} else if (!(PM_TYPE(v->pm->node.flags) & (PM_ARRAY|PM_HASHED)) &&
- !(v->pm->node.flags & (PM_SPECIAL|PM_TIED))) {
+ !(v->flags & VALFLAG_REFSLICE) &&
+ !(v->pm->node.flags & (PM_SPECIAL|PM_TIED))) {
int uniq = v->pm->node.flags & PM_UNIQUE;
if ((flags & ASSPM_AUGMENT) && !(v->pm->node.flags & PM_UNSET)) {
/* insert old value at the beginning of the val array */
@@ -3391,8 +3400,12 @@ assignaparam(char *s, char **val, int flags)
free(val);
val = new;
}
- unsetparam(t);
- createparam(t, PM_ARRAY | uniq);
+ if (resetparam(v->pm, PM_ARRAY | uniq)) {
+ unqueue_signals();
+ freearray(val);
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
v = NULL;
}
}
@@ -3600,11 +3613,15 @@ sethparam(char *s, char **val)
if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING))) {
createparam(t, PM_HASHED);
checkcreate = 1;
- } else if (!(PM_TYPE(v->pm->node.flags) & PM_HASHED)) {
+ } else if (!(PM_TYPE(v->pm->node.flags) & PM_HASHED) &&
+ !(v->flags & VALFLAG_REFSLICE)) {
if (!(v->pm->node.flags & PM_SPECIAL)) {
- unsetparam(t);
- /* no WARNCREATEGLOBAL check here as parameter already existed */
- createparam(t, PM_HASHED);
+ if (resetparam(v->pm, PM_HASHED)) {
+ unqueue_signals();
+ freearray(val);
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
v = NULL;
} else {
zerr("%s: can't change type of a special parameter", t);
@@ -3761,6 +3778,29 @@ setiparam_no_convert(char *s, zlong val)
return assignsparam(s, ztrdup(buf), ASSPM_WARN);
}
+/* Reset a parameter */
+
+/**/
+mod_export int
+resetparam(Param pm, int flags)
+{
+ char *s = pm->node.nam;
+ queue_signals();
+ if (pm != (Param)(paramtab == realparamtab ?
+ /* getnode2() to avoid autoloading */
+ paramtab->getnode2(paramtab, s) :
+ paramtab->getnode(paramtab, s))) {
+ unqueue_signals();
+ zerr("can't change type of hidden variable: %s", s);
+ return 1;
+ }
+ s = dupstring(s);
+ unsetparam_pm(pm, 0, 1);
+ unqueue_signals();
+ createparam(s, flags);
+ return 0;
+}
+
/* Unset a parameter */
/**/
diff --git a/Src/zsh.h b/Src/zsh.h
index ebb63f498..993108a8c 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -742,18 +742,19 @@ struct multio {
/* lvalue for variable assignment/expansion */
struct value {
- int isarr;
Param pm; /* parameter node */
+ char **arr; /* cache for hash turned into array */
+ int isarr;
int flags; /* flags defined below */
int start; /* first element of array slice, or -1 */
int end; /* 1-rel last element of array slice, or -1 */
- char **arr; /* cache for hash turned into array */
};
enum {
VALFLAG_INV = 0x0001, /* We are performing inverse subscripting */
VALFLAG_EMPTY = 0x0002, /* Subscripted range is empty */
- VALFLAG_SUBST = 0x0004 /* Substitution, so apply padding, case flags */
+ VALFLAG_SUBST = 0x0004, /* Substitution, so apply padding, case flags */
+ VALFLAG_REFSLICE= 0x0008 /* Value is a reference to an array slice */
};
#define MAX_ARRLEN 262144
diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst
index de9707c5e..5d229a94e 100644
--- a/Test/K01nameref.ztst
+++ b/Test/K01nameref.ztst
@@ -753,9 +753,9 @@ F:Same test, should part 5 output look like this?
outer() { local foo=outer; inner foo; typeset -p foo; }
foo=3 ; { outer foo } always { typeset -p foo }
)
-1:up-reference part 11, assignment to enclosing scope, type mismatch
+0:up-reference part 11, assignment to enclosing scope, type mismatch
+>typeset -a foo=( alpha beta gamma )
>typeset -g foo=3
-?inner: foo: attempt to assign array value to non-array
(
inner() { local -n var="${1:?}"; unset var; var=(alpha beta gamma); }
@@ -1372,4 +1372,159 @@ F:previously this could create an infinite recursion and crash
>reached
*?*: ref1: invalid self reference
+ typeset -A -g VAR0=(aa AA)
+ typeset -n -g REF0=VAR0
+ typeset -A -g var0=(aa AA)
+ typeset -A var1=(aa AA)
+ () {
+ typeset -A var2=(aa AA)
+ typeset -n ref0=var0 ref1=var1 ref2=var2
+ # Test initial association
+ typeset -pm VAR0 var\?
+ REF0=(zz ZZ); ref0=(zz ZZ); ref1=(zz ZZ); ref2=(zz ZZ);
+ typeset -pm VAR0 var\?
+ # Test change from association to string
+ REF0=foo; ref0=foo; ref1=foo; ref2=foo;
+ typeset -pm VAR0 var\?
+ REF0=bar; ref0=bar; ref1=bar; ref2=bar;
+ typeset -pm VAR0 var\?
+ # Test change from string to array
+ REF0=(aa AA); ref0=(aa AA); ref1=(aa AA); ref2=(aa AA);
+ typeset -pm VAR0 var\?
+ REF0=(zz ZZ); ref0=(zz ZZ); ref1=(zz ZZ); ref2=(zz ZZ);
+ typeset -pm VAR0 var\?
+ # Test change from array to string
+ REF0=foo; ref0=foo; ref1=foo; ref2=foo;
+ typeset -pm VAR0 var\?
+ }
+0:type changes via assignments to references
+>typeset -g -A VAR0=( [aa]=AA )
+>typeset -g -A var0=( [aa]=AA )
+>typeset -g -A var1=( [aa]=AA )
+>typeset -A var2=( [aa]=AA )
+>typeset -g -A VAR0=( [zz]=ZZ )
+>typeset -g -A var0=( [zz]=ZZ )
+>typeset -g -A var1=( [zz]=ZZ )
+>typeset -A var2=( [zz]=ZZ )
+>typeset -g VAR0=foo
+>typeset -g var0=foo
+>typeset -g var1=foo
+>typeset var2=foo
+>typeset -g VAR0=bar
+>typeset -g var0=bar
+>typeset -g var1=bar
+>typeset var2=bar
+>typeset -g -a VAR0=( aa AA )
+>typeset -g -a var0=( aa AA )
+>typeset -g -a var1=( aa AA )
+>typeset -a var2=( aa AA )
+>typeset -g -a VAR0=( zz ZZ )
+>typeset -g -a var0=( zz ZZ )
+>typeset -g -a var1=( zz ZZ )
+>typeset -a var2=( zz ZZ )
+>typeset -g VAR0=foo
+>typeset -g var0=foo
+>typeset -g var1=foo
+>typeset var2=foo
+
+ typeset -A -g ass0=(aa AA)
+ typeset -A ass1=(aa AA)
+ typeset -a -g arr0=(aa AA)
+ typeset -a arr1=(aa AA)
+ typeset -g str0=foo
+ typeset str1=foo
+ () {
+ typeset -n Ass0=ass0 Ass1=ass1 Arr0=arr0 Arr1=arr1 Str0=str0 Str1=str1
+ typeset ass0 ass1 arr0 arr1 str0 str1
+ { Ass0=foo } always { TRY_BLOCK_ERROR=0 }; echo $?
+ { Ass1=foo } always { TRY_BLOCK_ERROR=0 }; echo $?
+ { Arr0=foo } always { TRY_BLOCK_ERROR=0 }; echo $?
+ { Arr1=foo } always { TRY_BLOCK_ERROR=0 }; echo $?
+ { Str0=(x) } always { TRY_BLOCK_ERROR=0 }; echo $?
+ { Str1=(x) } always { TRY_BLOCK_ERROR=0 }; echo $?
+ }
+ typeset -p ass0 ass1 arr0 arr1 str0 str1
+0:can't change type of hidden variables via assignments to references
+>1
+>1
+>1
+>1
+>1
+>1
+>typeset -g -A ass0=( [aa]=AA )
+>typeset -A ass1=( [aa]=AA )
+>typeset -g -a arr0=( aa AA )
+>typeset -a arr1=( aa AA )
+>typeset -g str0=foo
+>typeset str1=foo
+?(anon):3: can't change type of hidden variable: ass0
+?(anon):4: can't change type of hidden variable: ass1
+?(anon):5: can't change type of hidden variable: arr0
+?(anon):6: can't change type of hidden variable: arr1
+?(anon):7: can't change type of hidden variable: str0
+?(anon):8: can't change type of hidden variable: str1
+
+ typeset -A ass=(aa AA)
+ typeset -a arr=(aa AA)
+ typeset -i int=42
+ typeset str=foo
+ typeset -n Ass=ass Arr=arr Int=int Str=str
+ { typeset Ass=foo } always { TRY_BLOCK_ERROR=0 }; echo $?
+ { typeset Arr=foo } always { TRY_BLOCK_ERROR=0 }; echo $?
+ { typeset Int=(aa AA) } always { TRY_BLOCK_ERROR=0 }; echo $?
+ { typeset Str=(aa AA) } always { TRY_BLOCK_ERROR=0 }; echo $?
+ typeset -p ass arr int str
+0:type changes via plain typeset to references
+F:converting from association/array to string should work here too
+>1
+>1
+>0
+>0
+>typeset -A ass=( [aa]=AA )
+>typeset -a arr=( aa AA )
+>typeset -a int=( aa AA )
+>typeset -a str=( aa AA )
+?(eval):typeset:6: ass: inconsistent type for assignment
+?(eval):typeset:7: arr: inconsistent type for assignment
+
+ typeset var
+ typeset -n ref=var
+ # Change type to string
+ unset var; typeset var=fubar; typeset ref=barfu; typeset -p var
+ unset var; typeset -i var=12345; typeset +i ref=barfu; typeset -p var
+ unset var; typeset -a var=(a A); typeset +a ref=barfu; typeset -p var
+ unset var; typeset -A var=(a A); typeset +A ref=barfu; typeset -p var
+ # Change type to integer
+ unset var; typeset var=fubar; typeset -i ref=56789; typeset -p var
+ unset var; typeset -i var=12345; typeset -i ref=56789; typeset -p var
+ unset var; typeset -a var=(a A); typeset -i ref=56789; typeset -p var
+ unset var; typeset -A var=(a A); typeset -i ref=56789; typeset -p var
+ # Change type to array
+ unset var; typeset var=fubar; typeset -a ref=(z Z); typeset -p var
+ unset var; typeset -i var=12345; typeset -a ref=(z Z); typeset -p var
+ unset var; typeset -a var=(a A); typeset -a ref=(z Z); typeset -p var
+ unset var; typeset -A var=(a A); typeset -a ref=(z Z); typeset -p var
+ # Change type to association
+ unset var; typeset var=fubar; typeset -A ref=(z Z); typeset -p var
+ unset var; typeset -i var=12345; typeset -A ref=(z Z); typeset -p var
+ unset var; typeset -a var=(a A); typeset -A ref=(z Z); typeset -p var
+ unset var; typeset -A var=(a A); typeset -A ref=(z Z); typeset -p var
+0:type changes via typed typeset to references
+>typeset var=barfu
+>typeset var=barfu
+>typeset var=barfu
+>typeset var=barfu
+>typeset -i var=56789
+>typeset -i var=56789
+>typeset -i var=56789
+>typeset -i var=56789
+>typeset -a var=( z Z )
+>typeset -a var=( z Z )
+>typeset -a var=( z Z )
+>typeset -a var=( z Z )
+>typeset -A var=( [z]=Z )
+>typeset -A var=( [z]=Z )
+>typeset -A var=( [z]=Z )
+>typeset -A var=( [z]=Z )
+
%clean
Messages sorted by:
Reverse Date,
Date,
Thread,
Author