Here is an updated version based on workers/54262.- Fix rebound refs to behave the same as newly bound ones.PhilippeOn 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.
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:
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.
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