diff options
| -rw-r--r-- | Src/builtin.c | 24 | ||||
| -rw-r--r-- | Src/params.c | 37 | ||||
| -rw-r--r-- | Test/K01nameref.ztst | 226 |
3 files changed, 263 insertions, 24 deletions
diff --git a/Src/builtin.c b/Src/builtin.c index d62e76b92..7eb839a61 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -3836,10 +3836,9 @@ bin_unset(char *name, char **argv, Options ops, int func) /* record pointer to next, since we may free this one */ next = (Param) pm->node.next; if (pattry(pprog, pm->node.nam)) { - if (!OPT_ISSET(ops,'n') && - (pm->node.flags & PM_NAMEREF) && pm->u.str) - unsetparam(pm->u.str); - else + if (OPT_ISSET(ops,'n') || + ((pm = resolve_nameref(pm)) && + !(pm->node.flags & PM_NAMEREF))) unsetparam_pm(pm, 0, 1); match++; } @@ -3931,22 +3930,11 @@ bin_unset(char *name, char **argv, Options ops, int func) zerrnam(name, "%s: invalid element for unset", s); returnval = 1; } - } else { - if (!OPT_ISSET(ops,'n')) { - int ref = (pm->node.flags & PM_NAMEREF); - if (!(pm = resolve_nameref(pm))) - continue; - if (ref && pm->level < locallevel && - !(pm->node.flags & PM_READONLY)) { - /* Just mark unset, do not remove from table */ - stdunsetfn(pm, 0); - pm->node.flags |= PM_DECLARED; - continue; - } - } + } else if (OPT_ISSET(ops,'n') || + ((pm = resolve_nameref(pm)) && + !(pm->node.flags & PM_NAMEREF))) if (unsetparam_pm(pm, 0, 1)) returnval = 1; - } if (ss) *ss = '['; } diff --git a/Src/params.c b/Src/params.c index 05efd19c0..f838fa73f 100644 --- a/Src/params.c +++ b/Src/params.c @@ -504,6 +504,16 @@ static Param argvparam; * times in the same list. Non of that is harmful as long as only * instances that are still references referring to the ending scope * are updated when the scope ends. + * + * The list corresponding to the global scope never receives any of + * the named references described above. Instead, it's used to track + * global parameters that were unset via a named reference while in a + * scope where they were hidden by a nested parameter with the same + * name. In such cases, the global parameter's Param instance can't be + * deleted as usual. Instead, it's marked as unset and added to the + * global scope's list. Each time a scope ends, the list is traversed + * and parameters that are still unset but no longer hidden are + * deleted. */ static LinkList *scoperefs = NULL; static int scoperefs_num = 0; @@ -3835,6 +3845,24 @@ unsetparam_pm(Param pm, int altflag, int exp) (pm->node.flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL) return 0; + /* + * Global variables can only be deleted if they aren't hidden by a + * local one with the same name. + */ + if (!pm->level && + pm != (Param) (paramtab == realparamtab ? + /* getnode2() to avoid autoloading */ + paramtab->getnode2(paramtab, pm->node.nam) : + paramtab->getnode(paramtab, pm->node.nam))) { + LinkList refs; + if (!scoperefs) + scoperefs = zshcalloc((scoperefs_num = 8) * sizeof(refs)); + if (!scoperefs[0]) + scoperefs[0] = znewlinklist(); + zpushnode(scoperefs[0], pm); + return 0; + } + /* remove parameter node from table */ paramtab->removenode(paramtab, pm->node.nam); @@ -5809,6 +5837,15 @@ endparamscope(void) setscope(pm); } } + /* Delete unset global variables that were hidden at unset time */ + if ((refs = scoperefs ? scoperefs[0] : NULL)) { + scoperefs[0] = NULL; + for (Param pm; refs && (pm = (Param)getlinknode(refs));) { + if ((pm->node.flags & PM_UNSET) && !(pm->node.flags & PM_DECLARED)) + unsetparam_pm(pm, 1, 0); + } + freelinklist(refs, NULL); + } unqueue_signals(); } diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst index be4c635db..0336a507d 100644 --- a/Test/K01nameref.ztst +++ b/Test/K01nameref.ztst @@ -1043,15 +1043,15 @@ F:Checking for a bug in zmodload that affects later tests typeset -p .K01.{scalar,assoc,array,integer,double,float,readonly} unset .K01.{scalar,assoc,array,integer,double,float} 0:unset various types via nameref, including a readonly special ->typeset -g .K01.scalar ->typeset -g -A .K01.assoc ->typeset -g -a .K01.array ->typeset -g -i .K01.integer ->typeset -g -E .K01.double ->typeset -g -F .K01.float >typeset -g -r .K01.readonly=RO *?*read-only variable: ARGC *?*read-only variable: .K01.readonly +*?*no such variable: .K01.scalar +*?*no such variable: .K01.assoc +*?*no such variable: .K01.array +*?*no such variable: .K01.integer +*?*no such variable: .K01.double +*?*no such variable: .K01.float unset -n ref unset one @@ -1951,6 +1951,220 @@ F:converting from association/array to string should work here too ># d:reference to not-yet-defined - local - ref1 >typeset -i var=42 + test-unset() { + typeset var0=foo + typeset -n ref1=var0 ref2=ref1 + typeset cmd=(unset $@); echo "#" $cmd; $cmd + typeset -p var0 ref1 ref2 + } + test-unset -n ref1 + test-unset -n ref2 + test-unset -n -m ref1 + test-unset -n -m ref2 + unfunction test-unset +0:unsetting references with -n unsets the references +># unset -n ref1 +>typeset var0=foo +>typeset -n ref2=ref1 +># unset -n ref2 +>typeset var0=foo +>typeset -n ref1=var0 +># unset -n -m ref1 +>typeset var0=foo +>typeset -n ref2=ref1 +># unset -n -m ref2 +>typeset var0=foo +>typeset -n ref1=var0 + + test-unset() { + typeset var0=foo + typeset -n ref1=var0 ref2=ref1 + typeset cmd=(unset $@); echo "#" $cmd; $cmd + typeset -p var0 ref1 ref2 + } + test-unset ref1 + test-unset ref2 + test-unset -m ref1 + test-unset -m ref2 + unfunction test-unset +0:unsetting references without -n unsets the referred parameters +># unset ref1 +>typeset -n ref1=var0 +>typeset -n ref2=ref1 +># unset ref2 +>typeset -n ref1=var0 +>typeset -n ref2=ref1 +># unset -m ref1 +>typeset -n ref1=var0 +>typeset -n ref2=ref1 +># unset -m ref2 +>typeset -n ref1=var0 +>typeset -n ref2=ref1 + + test-unset() { + typeset var0=12345 + typeset -n ref1=var0 ref2=ref1 + typeset cmd=(unset $@); echo "#" $cmd; $cmd + typeset -p var0 + } + test-unset ref1"[3]" + test-unset ref2"[3]" + test-unset -n ref1"[3]" + test-unset -n ref2"[3]" + unfunction test-unset +0:unsetting subscripted references unsets the referred elements +># unset ref1[3] +>typeset var0=1245 +># unset ref2[3] +>typeset var0=1245 +># unset -n ref1[3] +>typeset var0=1245 +># unset -n ref2[3] +>typeset var0=1245 + + test-unset() { + typeset -r var=foo + typeset -n ref=var + typeset cmd=(unset $@); echo "#" $cmd; { $cmd 2>&1 } always { TRY_BLOCK_ERROR=0 } + typeset -p var + } + test-unset var + test-unset -m var + test-unset ref + test-unset -m ref + test-unset var"[2]" + test-unset ref"[2]" + test-unset -n ref"[2]" + unfunction test-unset +0:unsetting read-only parameter triggers an error +># unset var +>test-unset:3: read-only variable: var +>typeset -r var=foo +># unset -m var +>test-unset:3: read-only variable: var +>typeset -r var=foo +># unset ref +>test-unset:3: read-only variable: var +>typeset -r var=foo +># unset -m ref +>test-unset:3: read-only variable: var +>typeset -r var=foo +># unset var[2] +>test-unset:3: read-only variable: var +>typeset -r var=foo +># unset ref[2] +>test-unset:3: read-only variable: var +>typeset -r var=foo +># unset -n ref[2] +>test-unset:3: read-only variable: var +>typeset -r var=foo + + test-unset() { + typeset -n ref1 ref2=ref1 + typeset cmd=(unset $@); echo "#" $cmd; $cmd + typeset -p ref1 ref2 + } + test-unset ref1 + test-unset ref2 + test-unset -m ref1 + test-unset -m ref2 + unfunction test-unset +0:unsetting placeholder references or their referents has no effect +># unset ref1 +>typeset -n ref1 +>typeset -n ref2=ref1 +># unset ref2 +>typeset -n ref1 +>typeset -n ref2=ref1 +># unset -m ref1 +>typeset -n ref1 +>typeset -n ref2=ref1 +># unset -m ref2 +>typeset -n ref1 +>typeset -n ref2=ref1 + + test-unset() { + typeset -n ref1=undefined ref2=ref1 + typeset cmd=(unset $@); echo "#" $cmd; $cmd + typeset -p ref1 ref2 + } + typeset -p undefined 2>&1 + test-unset ref1 + test-unset ref2 + test-unset -m ref1 + test-unset -m ref2 + unfunction test-unset +0:unsetting references to not-yet-defined variables or their referents has no effect +>(eval):typeset:6: no such variable: undefined +># unset ref1 +>typeset -n ref1=undefined +>typeset -n ref2=ref1 +># unset ref2 +>typeset -n ref1=undefined +>typeset -n ref2=ref1 +># unset -m ref1 +>typeset -n ref1=undefined +>typeset -n ref2=ref1 +># unset -m ref2 +>typeset -n ref1=undefined +>typeset -n ref2=ref1 + + test-unset() { + typeset -n refg1=g1 refl1=l1 + () { + typeset -g g1=glb1 g2=glb2 + typeset l1=lcl1 l2=lcl2 + () { + typeset -n refg2=g2 refl2=l2 + typeset cmd=(unset $@ refg1 refg2 refl1 refl2); echo "#" $cmd; $cmd + } $@ + typeset -p g1 g2 l1 l2 2>&1 + } $@ + unset g1 g2 + } + test-unset + test-unset -m + unfunction test-unset +0:unsetting references referring to parameters in enclosing scopes unsets the parameters +># unset refg1 refg2 refl1 refl2 +>(anon):typeset:7: no such variable: g1 +>(anon):typeset:7: no such variable: g2 +># unset -m refg1 refg2 refl1 refl2 +>(anon):typeset:7: no such variable: g1 +>(anon):typeset:7: no such variable: g2 + + test-unset() { + typeset -g g=glb + typeset l=lcl + typeset -n refg=g refl=l + () { + typeset g=hide-g + typeset l=hide-l + typeset cmd=(unset $@ refg refl); echo "#" $cmd; $cmd + echo "# inner scope" + typeset -p g l 2>&1 + } $@ + echo "# outer scope" + typeset -p g l 2>&1 + unset g + } + test-unset + test-unset -m + unfunction test-unset +0:unsetting references referring to hidden parameters unsets the hidden parameters +># unset refg refl +># inner scope +>typeset g=hide-g +>typeset l=hide-l +># outer scope +>test-unset:typeset:12: no such variable: g +># unset -m refg refl +># inner scope +>typeset g=hide-g +>typeset l=hide-l +># outer scope +>test-unset:typeset:12: no such variable: g + typeset -n ref1 typeset -n ref2 typeset -n ref3=ref2 |
