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



I have updated my patch to include tests:

- Fix type conversions via assignments to references.

The second to last test (type changes via plain typeset to references) shows that conversions to string from association/array are not supported via "typeset ref=foo", while conversions to array from string are supported. This behavior doesn't change with this patch but we probably want to enable the former. This can be done in a separate patch.

Philippe


On Sat, Jun 14, 2025 at 3:47 AM Bart Schaefer <schaefer@xxxxxxxxxxxxxxxx> wrote:
I'm pretty much offline the next several days but if you could create a list by zsh-workers article number of your patches in the order they should be applied, it would make reviewing them much easier
diff --git a/Src/params.c b/Src/params.c
index 7b515515e..a52c4fd5e 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -2245,6 +2245,7 @@ fetchvalue(Value v, char **pptr, int bracks, int flags)
 		    flags |= SCANPM_NOEXEC;
 		    *ss = sav;
 		    s = dyncat(ss,*pptr);
+		    v->isrefslice = 1;
 		} else
 		    s = *pptr;
 	    }
@@ -3217,12 +3218,17 @@ 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)) && 
+	} else if ((((v->pm->node.flags & PM_ARRAY) && !v->isrefslice &&
+		     !(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);
+	    if (resetparam(v->pm, PM_SCALAR)) {
+		unqueue_signals();
+		zsfree(val);
+		errflag |= ERRFLAG_ERROR;
+		return NULL;
+	    }
 	    /* not regarded as a new creation */
 	    v = NULL;
 	}
@@ -3369,7 +3375,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->isrefslice &&
+		   !(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 */
@@ -3382,8 +3389,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;
 	}
     }
@@ -3591,11 +3602,14 @@ 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->isrefslice) {
 	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);
@@ -3752,6 +3766,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 4e5c02980..3bd5412b2 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -748,6 +748,7 @@ struct value {
     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 */
+    int isrefslice;
 };
 
 enum {
diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst
index 54f0aaf68..536352ffa 100644
--- a/Test/K01nameref.ztst
+++ b/Test/K01nameref.ztst
@@ -734,9 +734,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); }
@@ -1179,4 +1179,159 @@ F:previously this could create an infinite recursion and crash
 >typeset PS1=zz
 *?*
 
+ 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