Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: PATCH: Rebound references don't behave like newly bound ones



Here is the updated patch that I forgot to attach.

Philippe


On Sat, Mar 28, 2026 at 12:52 AM Philippe Altherr <philippe.altherr@xxxxxxxxx> wrote:
Here is an updated version based on workers/54262.

Fix rebound refs to behave the same as newly bound ones.

Philippe


On Sat, Nov 8, 2025 at 4:55 PM Philippe Altherr <philippe.altherr@xxxxxxxxx> wrote:
Two references referencing the same variable should always behave the same. This is currently not always the case if one of them previously referenced a different variable that went out of scope.

Example

typeset -n ref1 ref2

local var=l1

() {

  () {

    local var=l3

    ref1=var

    echo A1: ref1=$ref1 ref2=$ref2

  }

  ref2=var

  # At this point, "ref1" and "ref2" refer to the same variable

  # "var". Going forward, they should behave the same.

  echo A2: ref1=$ref1 ref2=$ref2

  () {

    local var=l3

    echo A3: ref1=$ref1 ref2=$ref2

  }

  echo A4: ref1=$ref1 ref2=$ref2

  local var=l2

  echo A5: ref1=$ref1 ref2=$ref2

}

echo A6: ref1=$ref1 ref2=$ref2


Current output

Expected output

A1: ref1=l3 ref2=

A2: ref1=l1 ref2=l1

A3: ref1=l3 ref2=l1

A4: ref1=l1 ref2=l1

A5: ref1=l2 ref2=l1

A6: ref1=l1 ref2=l1

A1: ref1=l3 ref2=

A2: ref1=l1 ref2=l1

A3: ref1=l1 ref2=l1

A4: ref1=l1 ref2=l1

A5: ref1=l1 ref2=l1

A6: ref1=l1 ref2=l1


Fixing this discrepancy requires adopting what was described as the "all the way up" strategy for rebound variables. Thankfully, once the fix for hidden references is in (workers/54047), implementing "all the way up" is trivial:

diff --git a/Src/params.c b/Src/params.c

index 5e9387339..c737bf748 100644

--- a/Src/params.c

+++ b/Src/params.c

@@ -5853,7 +5853,8 @@ endparamscope(void)

     for (Param pm; refs && (pm = (Param)getlinknode(refs));) {

  if ((pm->node.flags & PM_NAMEREF) && !(pm->node.flags & PM_UPPER) &&

      pm->base > locallevel) {

-     setscope_base(pm, locallevel);

+     pm->base = 0;

+     setscope(pm);

  }

     }

     unqueue_signals();


This change also has for effect to detect and signal incidental reference loops (i.e., reference loops created when a reference is rebound) as soon as the loop is created rather than only when it's first accessed.

Example

typeset -n ref1

typeset -n ref2=ref1;

() {

  typeset ref2=foo

  ref1=ref2

  echo ref1=$ref1

}

echo ref1 and ref2 now form a loop

: $ref1

echo NOT REACHED


Current output

New output

ref1=foo

ref1 and ref2 now form a loop

zsh: ref1: invalid self reference

ref1=foo

zsh: ref2: invalid self reference



Philippe


diff --git a/Src/params.c b/Src/params.c
index 4f5454abb..461e02acf 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -5905,7 +5905,8 @@ endparamscope(void)
     for (Param pm; refs && (pm = (Param)getlinknode(refs));) {
 	if ((pm->node.flags & PM_NAMEREF) && !(pm->node.flags & PM_UNSET) &&
 	    !(pm->node.flags & PM_UPPER) && pm->base > locallevel) {
-	    setscope_base(pm, locallevel);
+	    pm->base = 0;
+	    setscope(pm);
 	}
     }
     unqueue_signals();
diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst
index 82ccbfb89..0b4475827 100644
--- a/Test/K01nameref.ztst
+++ b/Test/K01nameref.ztst
@@ -1258,6 +1258,81 @@ F:previously this could create an infinite recursion and crash
 >h2: ref1=f1 ref2=f1 ref3=f1
 >f1: ref1=f1 ref2=f1 ref3=f1
 
+ () {
+   typeset -n ref1 ref2
+   local var=l1
+   () {
+     () {
+       local var=l3
+       ref1=var
+       echo A1: ref1=$ref1 ref2=$ref2
+     }
+     ref2=var
+     # At this point, "ref1" and "ref2" refer to the same variable
+     # "var". Going forward, they should behave the same.
+     echo A2: ref1=$ref1 ref2=$ref2
+     () {
+       local var=l3
+       echo A3: ref1=$ref1 ref2=$ref2
+     }
+     echo A4: ref1=$ref1 ref2=$ref2
+     local var=l2
+     echo A5: ref1=$ref1 ref2=$ref2
+   }
+   echo A6: ref1=$ref1 ref2=$ref2
+ }
+ () {
+   typeset -n ref1 ref2
+   () {
+     () {
+       local var=l3
+       ref1=var
+       echo B1: ref1=$ref1 ref2=$ref2
+     }
+     ref2=var
+     # At this point, "ref1" and "ref2" refer to the same undefined
+     # variable "var". Going forward, they should behave the same.
+     echo B2: ref1=$ref1 ref2=$ref2
+     () {
+       () {
+         local var=l4
+         echo B3: ref1=$ref1 ref2=$ref2
+       }
+       local var=l3
+       echo B4: ref1=$ref1 ref2=$ref2
+     }
+     local var=l2
+     echo B5: ref1=$ref1 ref2=$ref2
+     () {
+       () {
+         local var=l4
+         echo B6: ref1=$ref1 ref2=$ref2
+       }
+       local var=l3
+       echo B7: ref1=$ref1 ref2=$ref2
+     }
+     echo B8: ref1=$ref1 ref2=$ref2
+   }
+   local var=l1
+   echo B9: ref1=$ref1 ref2=$ref2
+ }
+0:rebound nameref behaves the same as newly bound one
+>A1: ref1=l3 ref2=
+>A2: ref1=l1 ref2=l1
+>A3: ref1=l1 ref2=l1
+>A4: ref1=l1 ref2=l1
+>A5: ref1=l1 ref2=l1
+>A6: ref1=l1 ref2=l1
+>B1: ref1=l3 ref2=
+>B2: ref1= ref2=
+>B3: ref1=l4 ref2=l4
+>B4: ref1=l3 ref2=l3
+>B5: ref1=l2 ref2=l2
+>B6: ref1=l2 ref2=l2
+>B7: ref1=l2 ref2=l2
+>B8: ref1=l2 ref2=l2
+>B9: ref1=l1 ref2=l1
+
 #
 # The following two tests are linked, do not separate
 #
@@ -1500,39 +1575,12 @@ F:$$[1] reference should print the first digit of $$ but prints nothing
  () {
    typeset ref2=foo
    ref1=ref2
+   echo reached
  }
- echo reached
- echo $ref1
- echo NOT REACHED
-1:expansion of incidental reference loop triggers error
->reached
-*?*: ref1: invalid self reference
-
- typeset -n ref1
- typeset -n ref2=ref1;
- () {
-   typeset ref2=foo
-   ref1=ref2
- }
- echo reached
- ref1=foo
- echo NOT REACHED
-1:assignment to incidental reference loop triggers error
->reached
-*?*: ref1: invalid self reference
-
- typeset -n ref1
- typeset -n ref2=ref1;
- () {
-   typeset ref2=foo
-   ref1=ref2
- }
- echo reached
- typeset -n ref3=ref1
  echo NOT REACHED
-1:reference to incidental reference loop triggers error
+1:incidental reference loop triggers error
 >reached
-*?*: ref1: invalid self reference
+*?*: ref2: invalid self reference
 
  typeset -A -g VAR0=(aa AA)
  typeset -n -g REF0=VAR0


Messages sorted by: Reverse Date, Date, Thread, Author