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

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



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


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

Philippe


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();
diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst
index accb68e07..d7d9ac1e9 100644
--- a/Test/K01nameref.ztst
+++ b/Test/K01nameref.ztst
@@ -1181,6 +1181,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 tests are run in interactive mode, using PS1 as an
 # assignable special with side-effects.  This crashed at one time.
@@ -1338,38 +1413,11 @@ F:previously this could create an infinite recursion and crash
  () {
    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
 
 %clean


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