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

PATCH: typeset -nu always relative to declaration



Before we start wandering into anything else ...

On Mon, May 12, 2025 at 9:19 AM Philippe Altherr
<philippe.altherr@xxxxxxxxx> wrote:
>
> I think that "relative to definition place" rather than "relative to initialization place" makes so much more sense for -u that we should definitely adopt it.

Attached patch implements this and includes a doc update, though more
might be needed.  Also fixes and introduces a test for the introduced
bug with "typeset -nu ref=nonexistent"
diff --git a/Doc/Zsh/func.yo b/Doc/Zsh/func.yo
index 7b71e34e9..9558b11c4 100644
--- a/Doc/Zsh/func.yo
+++ b/Doc/Zsh/func.yo
@@ -23,9 +23,11 @@ declared in an earlier function scope.
 (See noderef(Local Parameters).)
 
 A named parameter declared with the `tt(-n)' option to any of the
-`tt(typeset)' commands becomes a reference to a parameter in scope at
-the time of assignment to the named reference, which may be at a
-different call level than the declaring function.  For this reason,
+`tt(typeset)' acts as a reference to another parameter, which may
+be at a different call level than the declaring function.  When the
+`tt(-u)' option is also given, the referenced parameter is always
+found at a call level above the function where the reference is
+declared, otherwise the reference scope is dynamic.  For this reason,
 it is good practice to declare a named reference as soon as the
 referent parameter is in scope, and as early as possible in the
 function if the reference is to a parameter in a calling scope.
diff --git a/Src/params.c b/Src/params.c
index 1a2bf62d2..fec1b2c02 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -5899,9 +5899,8 @@ scanendscope(HashNode hn, UNUSED(int flags))
 	pm = hidden;
     if (pm && (pm->node.flags & PM_NAMEREF) &&
 	       pm->base >= pm->level && pm->base >= locallevel) {
+	/* Should never get here for a -u reference */
 	pm->base = locallevel;
-	if (pm->level < locallevel && (pm->node.flags & PM_UPPER))
-	    pm->node.flags &= ~PM_UPPER;
     }
 }
 
@@ -6307,7 +6306,9 @@ resolve_nameref(Param pm, const Asgment stop)
 	    if (pm) {
 		if (!(stop && (stop->flags & (PM_LOCAL)))) {
 		    int scope = ((pm->node.flags & PM_NAMEREF) ?
-				 ((pm->node.flags & PM_UPPER) ? -(pm->base) :
+				 ((pm->node.flags & PM_UPPER) ?
+				  /* pm->base == 0 means not set yet */
+				  -(pm->base ? pm->base : pm->level) :
 				  pm->base) : ((Param)hn)->level);
 		    hn = (HashNode)upscope((Param)hn, scope);
 		}
@@ -6413,14 +6414,14 @@ setscope(Param pm)
 	    } else if (!pm->base) {
 		pm->base = basepm->level;
 		if ((pm->node.flags & PM_UPPER) &&
-		    (basepm = upscope(basepm, -locallevel)))
+		    (basepm = upscope(basepm, -(pm->level))))
 		    pm->base = basepm->level;
 	    }
 	} else if (pm->base < locallevel && refname &&
 		   (basepm = (Param)getparamnode(realparamtab, refname))) {
 	    pm->base = basepm->level;
 	    if ((pm->node.flags & PM_UPPER) &&
-		(basepm = upscope(basepm, -locallevel)))
+		(basepm = upscope(basepm, -(pm->level))))
 		pm->base = basepm->level;
 	}
 	if (pm->base > pm->level) {
diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst
index 30b6673e0..54f0aaf68 100644
--- a/Test/K01nameref.ztst
+++ b/Test/K01nameref.ztst
@@ -767,6 +767,18 @@ F:typeset cannot bypass a name in the local scope, even via nameref
 >typeset -a foo=( alpha beta gamma )
 >typeset -g foo=3
 
+ () {
+  # scope with no parameters
+  () {
+    local -nu upref=$1
+    local var=at_upref
+    print -- $upref
+  } var
+ }
+0:up-reference part 15, non-existent parameter in outer scope
+# no output expected
+>
+
  if [[ $options[typesettounset] != on ]]; then
    ZTST_skip='Ignoring zmodload bug that resets TYPESET_TO_UNSET'
    setopt typesettounset
@@ -1088,16 +1100,16 @@ F:previously this could create an infinite recursion and crash
 >h:1: rs= - ra= - rs1= - ra1=
 >h:2: rs= - ra= - rs1= - ra1=
 >i:1: rs= - ra= - rs1= - ra1=
->i:2: rs=h - ra=h - rs1=h - ra1=h
->j:1: rs=h - ra=h - rs1=h - ra1=h
->j:2: rs=h - ra=h - rs1=h - ra1=h
->i:3: rs=h - ra=h - rs1=h - ra1=h
->k:1: rs=h - ra=h - rs1=h - ra1=h
->k:2: rs=h - ra=h - rs1=h - ra1=h
->h:3: rs=h - ra=h - rs1=h - ra1=h
->k:1: rs=h - ra=h - rs1=h - ra1=h
->k:2: rs=h - ra=h - rs1=h - ra1=h
->g:3: rs=g - ra=g - rs1=g - ra1=g
+>i:2: rs=g - ra=g - rs1=g - ra1=g
+>j:1: rs=g - ra=g - rs1=g - ra1=g
+>j:2: rs=g - ra=g - rs1=g - ra1=g
+>i:3: rs=g - ra=g - rs1=g - ra1=g
+>k:1: rs=g - ra=g - rs1=g - ra1=g
+>k:2: rs=g - ra=g - rs1=g - ra1=g
+>h:3: rs=g - ra=g - rs1=g - ra1=g
+>k:1: rs=g - ra=g - rs1=g - ra1=g
+>k:2: rs=g - ra=g - rs1=g - ra1=g
+>g:3: rs=f - ra=f - rs1=f - ra1=f
 
  e '' 6
 0:assignment at different scope than declaration, '' 6


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