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

LOCAL_VARS option ?



Phil suggested on IRC a LOCAL_VARS option that has the effect of making
all newly-declared variables local; e.g.,

% unset x y
% () { setopt localvars; x=42; typeset -g y=43 }
% echo $+x $+y
0 1
% 

I'm attaching a proof of concept patch (work in progress; see top of the
attachment for known issues), but WDYT of the the general concept?
[[[
WIP: LOCAL_VARS option

Proof of concept.  Known issues:

1. Interaction with 'emulate -L' (see TODO below).

2. There's a block in assignstrvalue() that I don't know whether needs
changing or not (see TODO below).

3. Currently, LOCAL_VARS overrides 'typeset -g', which is silly.  This
shouldn't be hard to fix by having bin_typeset() propagate a "leave
PM_LOCAL unset" flag to createparam().  (Currently, createparam() is
called with flags=PM_LOCAL for 'typeset' and with flags=0 both from
'typeset -g' and from callers other than bin_typeset().)

Finally, I'm not that familiar with the *param() C functions so I could
easily have overlooked something.
]]]

diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo
index 3a3130a..ac93810 100644
--- a/Doc/Zsh/builtins.yo
+++ b/Doc/Zsh/builtins.yo
@@ -552,6 +552,8 @@ function, if any; normally these options are turned off in all emulation
 modes except tt(ksh). The tt(-L) switch is mutually exclusive with the
 use of tt(-c) in var(flags).
 
+em(TODO): should tt(emulate -L) set tt(LOCAL_VARS)?
+
 If there is a single argument and the tt(-l) switch is given, the
 options that would be set or unset (the latter indicated with the prefix
 `tt(no)') are listed.  tt(-l) can be combined with tt(-L) or tt(-R) and
diff --git a/Doc/Zsh/options.yo b/Doc/Zsh/options.yo
index 434b710..46e5de5 100644
--- a/Doc/Zsh/options.yo
+++ b/Doc/Zsh/options.yo
@@ -1705,8 +1705,9 @@ item(tt(LOCAL_OPTIONS) <K>)(
 If this option is set at the point of return from a shell function,
 most options (including this one) which were in force upon entry to
 the function are restored; options that are not restored are
-tt(PRIVILEGED) and tt(RESTRICTED).  Otherwise, only this option,
-and the tt(LOCAL_LOOPS), tt(XTRACE) and tt(PRINT_EXIT_VALUE) options are
+tt(PRIVILEGED) and tt(RESTRICTED).  Otherwise, only the options
+tt(LOCAL_LOOPS), tt(LOCAL_OPTIONS), tt(LOCAL_VARS), tt(PRINT_EXIT_VALUE),
+and tt(XTRACE) are
 restored.  Hence if this is explicitly unset by a shell function the
 other options in force at the point of return will remain so.
 A shell function can also guarantee itself a known shell configuration
@@ -1745,6 +1746,18 @@ fn+LPAR()RPAR() { setopt localtraps; trap '' INT; sleep 3; })
 
 will restore normal handling of tt(SIGINT) after the function exits.
 )
+pindex(LOCAL_VARS)
+pindex(NO_LOCAL_VARS)
+pindex(LOCALVARS)
+pindex(NOLOCALVARS)
+item(tt(LOCAL_VARS))(
+Whilst this option is set, any shell construct that declares a new shell
+parameter will default to making that parameter local to the current function.
+Declaring a parameter that is visible to outer scopes is still possible with
+tt(typeset -g) and tt(typeset -x).
+
+This option overrides tt(ALL_EXPORT).
+)
 pindex(MULTI_FUNC_DEF)
 pindex(NO_MULTI_FUNC_DEF)
 pindex(MULTIFUNCDEF)
diff --git a/Src/builtin.c b/Src/builtin.c
index 3b5b2c4..d4b290e 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -5779,6 +5779,7 @@ bin_emulate(char *nam, char **argv, Options ops, UNUSED(int func))
 	if (opt_L)
 	    cmdopts[LOCALOPTIONS] = cmdopts[LOCALTRAPS] =
 		cmdopts[LOCALPATTERNS] = 1;
+	    /* XXX LOCALVARS */
 	if (opt_l) {
 	    list_emulate_options(cmdopts, opt_R);
 	    return 0;
@@ -5831,6 +5832,7 @@ bin_emulate(char *nam, char **argv, Options ops, UNUSED(int func))
     } else {
 	if (opt_L)
 	    opts[LOCALOPTIONS] = opts[LOCALTRAPS] = opts[LOCALPATTERNS] = 1;
+	    /* XXX LOCALVARS */
 	return 0;
     }
 
diff --git a/Src/exec.c b/Src/exec.c
index d3538c3..f812205 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -2429,7 +2429,6 @@ addvars(Estate state, Wordcode pc, int addflags)
 	if (isstr && (empty(vl) || !nextnode(firstnode(vl)))) {
 	    Param pm;
 	    char *val;
-	    int allexp;
 
 	    if (empty(vl))
 		val = ztrdup("");
@@ -2442,6 +2441,8 @@ addvars(Estate state, Wordcode pc, int addflags)
 		fputc(' ', xtrerr);
 	    }
 	    if ((addflags & ADDVAR_EXPORT) && !strchr(name, '[')) {
+		int old_LOCALVARS;
+		int allexp;
 		if ((addflags & ADDVAR_RESTRICT) && isset(RESTRICTED) &&
 		    (pm = (Param) paramtab->removenode(paramtab, name)) &&
 		    (pm->node.flags & PM_RESTRICTED)) {
@@ -2455,11 +2456,14 @@ addvars(Estate state, Wordcode pc, int addflags)
 		    STTYval = ztrdup(val);
 		}
 		allexp = opts[ALLEXPORT];
+		old_LOCALVARS = opts[LOCALVARS];
 		opts[ALLEXPORT] = 1;
+		opts[LOCALVARS] = 0;
 		if (isset(KSHARRAYS))
 		    unsetparam(name);
 	    	pm = assignsparam(name, val, myflags);
 		opts[ALLEXPORT] = allexp;
+		opts[LOCALVARS] = old_LOCALVARS;
 	    } else
 	    	pm = assignsparam(name, val, myflags);
 	    if (errflag) {
@@ -5532,6 +5536,7 @@ doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval)
 	    opts[XTRACE] = saveopts[XTRACE];
 	    opts[PRINTEXITVALUE] = saveopts[PRINTEXITVALUE];
 	    opts[LOCALOPTIONS] = saveopts[LOCALOPTIONS];
+	    opts[LOCALVARS] = saveopts[LOCALVARS];
 	    opts[LOCALLOOPS] = saveopts[LOCALLOOPS];
 	}
 
diff --git a/Src/options.c b/Src/options.c
index 4729ba5..ef7c91c 100644
--- a/Src/options.c
+++ b/Src/options.c
@@ -186,6 +186,7 @@ static struct optname optns[] = {
 {{NULL, "localloops",	      OPT_EMULATE},		 LOCALLOOPS},
 {{NULL, "localpatterns",      OPT_EMULATE},		 LOCALPATTERNS},
 {{NULL, "localtraps",	      OPT_EMULATE|OPT_KSH},	 LOCALTRAPS},
+{{NULL, "localvars",	      0},			 LOCALVARS},
 {{NULL, "login",	      OPT_SPECIAL},		 LOGINSHELL},
 {{NULL, "longlistjobs",	      0},			 LONGLISTJOBS},
 {{NULL, "magicequalsubst",    OPT_EMULATE},		 MAGICEQUALSUBST},
diff --git a/Src/params.c b/Src/params.c
index 946fdd1..30a6027 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -975,13 +975,22 @@ createparam(char *name, int flags)
 	    paramtab->addnode(paramtab, ztrdup(name), pm);
 	}
 
-	if (isset(ALLEXPORT) && !(flags & PM_HASHELEM))
+	if (isset(ALLEXPORT) && !(flags & PM_HASHELEM) &&
+	    unset(LOCALVARS))
 	    flags |= PM_EXPORTED;
     } else {
 	pm = (Param) hcalloc(sizeof *pm);
 	pm->node.nam = nulstring;
     }
-    pm->node.flags = flags & ~PM_LOCAL;
+    pm->node.flags = flags;
+    if ((flags & PM_HASHELEM) || (flags & PM_EXPORTED))
+	pm->node.flags &= ~PM_LOCAL;
+    else if (isset(LOCALVARS)) {
+	pm->node.flags |= PM_LOCAL;
+	pm->level = locallevel;
+    }
+    else
+	pm->node.flags &= ~PM_LOCAL;
 
     if(!(pm->node.flags & PM_SPECIAL))
 	assigngetset(pm);
@@ -2581,10 +2590,14 @@ assignstrvalue(Value v, char *val, int flags)
         }
 	break;
     }
+    /* TODO LOCALVARS: should isset(LOCALVARS) be checked here?
+     * (This block updates $PWD during cd.)
+     */
     if ((!v->pm->env && !(v->pm->node.flags & PM_EXPORTED) &&
-	 !(isset(ALLEXPORT) && !(v->pm->node.flags & PM_HASHELEM))) ||
+	 (unset(ALLEXPORT) || (v->pm->node.flags & PM_HASHELEM))) ||
 	(v->pm->node.flags & PM_ARRAY) || v->pm->ename)
 	return;
+    DPUTS1(0, "exporting %s", v->pm->node.nam);
     export_param(v->pm);
 }
 
@@ -4658,6 +4671,11 @@ pipestatsetfn(UNUSED(Param pm), char **x)
         numpipestats = 0;
 }
 
+/*
+ * If the shell scalar parameter s is exported, then set the corresponding
+ * environment variable to the array 't' joined by the tied array's joinchar.
+ */
+
 /**/
 void
 arrfixenv(char *s, char **t)
diff --git a/Src/zsh.h b/Src/zsh.h
index deefdba..3ab0415 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -2334,6 +2334,7 @@ enum {
     LOCALOPTIONS,
     LOCALPATTERNS,
     LOCALTRAPS,
+    LOCALVARS,
     LOGINSHELL,
     LONGLISTJOBS,
     MAGICEQUALSUBST,
diff --git a/Test/E01options.ztst b/Test/E01options.ztst
index 45df9f5..c9dc2bc 100644
--- a/Test/E01options.ztst
+++ b/Test/E01options.ztst
@@ -1213,3 +1213,50 @@
 ?(anon):4: `break' active at end of function scope
 ?(anon):4: `break' active at end of function scope
 ?(anon):4: `break' active at end of function scope
+
+  (unset x; () {                   x=42 }; echo "scalar born, unset: $+x")
+  (unset x; () { setopt localvars; x=42 }; echo "scalar born, set: $+x")
+  (local x; () {                   x=42 }; echo "scalar exists, unset: $+x")
+  (local x; () { setopt localvars; x=42 }; echo "scalar exists, set: $+x")
+  (unset x; () { setopt localvars; x=42 sh -c 'printf %s $x'; }; echo " in child, and now: $+x")
+0:LOCAL_VARS, scalars
+>scalar born, unset: 1
+>scalar born, set: 0
+>scalar exists, unset: 1
+>scalar exists, set: 1
+>42 in child, and now: 0
+
+  (unset x; () {                   x[5]=42 };    echo "array born, unset: $+x")
+  (unset x; () { setopt localvars; x[5]=42 };    echo "array born, set: $+x")
+  (typeset -a x; () {                   x[5]=42 }; echo "array exists, set: $+x ${+x[5]}")
+  (typeset -a x; () { setopt localvars; x[5]=42 }; echo "array exists, unset: $+x ${+x[5]}")
+0:LOCAL_VARS, classic arrays
+>array born, unset: 1
+>array born, set: 0
+>array exists, set: 1 1
+>array exists, unset: 1 1
+
+  (unset x; () {                   : ${(AA)=x::=foo bar}; }; echo "assoc born, unset: $+x")
+  (unset x; () { setopt localvars; : ${(AA)=x::=foo bar}; }; echo "assoc born, set: $+x")
+  (typeset -A x; () {                   x[foo]=42 }; echo "assoc exists, unset: $+x ${+x[foo]}")
+  (typeset -A x; () { setopt localvars; x[foo]=42 }; echo "assoc exists, set: $+x ${+x[foo]}")
+0:LOCAL_VARS, associative arrays
+>assoc born, unset: 1
+>assoc born, set: 0
+>assoc exists, unset: 1 1
+>assoc exists, set: 1 1
+
+# don't use LOCAL_TRAPS when testing it
+  () {
+    local flags
+    for flags in {,a,A}{g,x}; do
+      (unset x; () { typeset -$flags x; setopt localvars; }; echo $+x)
+    done
+  }
+0:LOCAL_VARS with -g/-x
+>1
+>1
+>1
+>1
+>1
+>1


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