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



Here is a patch rebased on the latest head.

> - Fix type conversions via assignments to references. - workers/53788

Holding this one for closer examination, for reasons of implementation
rather than outcome.

The patch adds a new field "isrefslice" to the  "value" struct. My first thought was to piggyback on the existing fields "start" and "end" but that didn't work out. Iirc, the problem was that these fields were sometimes (always?) initialized even for references to plain arrays (without any subscripts).

Fix type conversions via assignments to references.

Philippe



On Sun, Jun 15, 2025 at 3:23 PM Philippe Altherr <philippe.altherr@xxxxxxxxx> wrote:
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 5a0434e40..51f83adf2 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -2250,6 +2250,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;
 	    }
@@ -3222,12 +3223,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;
 	}
@@ -3378,7 +3384,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 */
@@ -3391,8 +3398,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 +3611,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);
@@ -3761,6 +3775,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..5aef4acfa 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 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