diff options
| author | Philippe Altherr <philippe.altherr@gmail.com> | 2026-03-31 14:50:08 -0700 |
|---|---|---|
| committer | Bart Schaefer <schaefer@zsh.org> | 2026-03-31 18:32:12 -0700 |
| commit | d8d74ef6820ad1f5b721237a7f2c5e45e47d39c3 (patch) | |
| tree | 28e7991e77cc9b07b8a23beaa28f4e2b952551aa | |
| parent | 54252: allow single dot as a valid (yet incomplete) parameter name (diff) | |
| download | zsh-d8d74ef6820ad1f5b721237a7f2c5e45e47d39c3.tar zsh-d8d74ef6820ad1f5b721237a7f2c5e45e47d39c3.tar.gz zsh-d8d74ef6820ad1f5b721237a7f2c5e45e47d39c3.tar.bz2 zsh-d8d74ef6820ad1f5b721237a7f2c5e45e47d39c3.tar.lz zsh-d8d74ef6820ad1f5b721237a7f2c5e45e47d39c3.tar.xz zsh-d8d74ef6820ad1f5b721237a7f2c5e45e47d39c3.tar.zst zsh-d8d74ef6820ad1f5b721237a7f2c5e45e47d39c3.zip | |
54261: `unset -n` removes named-reference type as well as removes referent
| -rw-r--r-- | ChangeLog | 6 | ||||
| -rw-r--r-- | Src/builtin.c | 1 | ||||
| -rw-r--r-- | Src/params.c | 63 | ||||
| -rw-r--r-- | Src/zsh.h | 5 | ||||
| -rw-r--r-- | Test/K01nameref.ztst | 228 | ||||
| -rw-r--r-- | Test/V10private.ztst | 12 |
6 files changed, 275 insertions, 40 deletions
@@ -1,3 +1,9 @@ +2026-03-31 Bart Schaefer <schaefer@zsh.org> + + * Philippe: 54261: Src/builtin.c, Src/params.c, Src/zsh.h, + Test/K01nameref.ztst, Test/V10private.ztst: `unset -n` removes + named-reference-ness of a parameter when removing the referent. + 2026-03-30 Jun-ichi Takimoto <takimoto-j@kba.biglobe.ne.jp> * 54252: Src/Zle/compcore.c: allow single dot as a valid (yet diff --git a/Src/builtin.c b/Src/builtin.c index c269dc1c6..7c095149d 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2038,6 +2038,7 @@ typeset_single(char *cname, char *pname, Param pm, int func, if ((pm = resolve_nameref(pm))) pname = pm->node.nam; if (pm && (pm->node.flags & PM_NAMEREF) && + (!(pm->node.flags & PM_UNSET) || (pm->node.flags & PM_DECLARED)) && (on & ~(PM_NAMEREF|PM_LOCAL|PM_READONLY))) { /* Changing type of PM_SPECIAL|PM_AUTOLOAD is a fatal error. * * Should this be a fatal error as well, rather than warning? */ diff --git a/Src/params.c b/Src/params.c index 3199fd17b..afc67eb14 100644 --- a/Src/params.c +++ b/Src/params.c @@ -1044,7 +1044,9 @@ createparam(char *name, int flags) if (oldpm && !(flags & PM_NAMEREF) && (oldpm->level == locallevel ? !(oldpm->node.flags & PM_RO_BY_DESIGN) : !(flags & PM_LOCAL)) && - (oldpm->node.flags & PM_NAMEREF)) { + (oldpm->node.flags & PM_NAMEREF) && + (!(oldpm->node.flags & PM_UNSET) || + (oldpm->node.flags & PM_DECLARED))) { /** * Here we only have to deal with namerefs that refer to * not-yet-defined or unset variable. All other namerefs @@ -1054,14 +1056,18 @@ createparam(char *name, int flags) **/ Param lastpm = resolve_nameref_rec(oldpm, NULL, 1); if (lastpm) { - if (lastpm->node.flags & PM_NAMEREF) { + if (lastpm->node.flags & PM_NAMEREF && + (!(lastpm->node.flags & PM_UNSET) || + (lastpm->node.flags & PM_DECLARED))) { char *refname = GETREFNAME(lastpm); if (refname && *refname) { + /* nameref pointing to a not-yet-defined variable */ name = refname; oldpm = NULL; } else { + /* nameref pointing to an uninitialized nameref */ if (!(lastpm->node.flags & PM_READONLY)) { - if (flags) { + if (flags & ~PM_LOCAL) { /* Only plain scalar assignment allowed */ zerr("%s: can't change type of named reference", name); /* Differs from ksh93u+ */ @@ -3386,6 +3392,10 @@ assignaparam(char *s, char **val, int flags) if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING))) { createparam(t, PM_ARRAY); created = 1; + } else if (v->pm->node.flags & PM_NAMEREF) { + zwarn("%s: can't change type of a named reference", t); + unqueue_signals(); + return NULL; } else if (!(PM_TYPE(v->pm->node.flags) & (PM_ARRAY|PM_HASHED)) && !(v->valflags & VALFLAG_REFSLICE) && !(v->pm->node.flags & (PM_SPECIAL|PM_TIED))) { @@ -6321,35 +6331,30 @@ resolve_nameref(Param pm) static Param resolve_nameref_rec(Param pm, const Param stop, int keep_lastref) { - Param hn = pm; - if (pm && (pm->node.flags & PM_NAMEREF)) { - char *refname = GETREFNAME(pm); - if (pm->node.flags & PM_TAGGED) { - zerr("%s: invalid self reference", pm->node.nam); - return NULL; - } else if (pm->node.flags & PM_UNSET) { - /* Semaphore with createparam() */ - pm->node.flags &= ~PM_UNSET; - return pm; - } + Param ref = pm; + char *refname; + if (!pm || !(pm->node.flags & PM_NAMEREF) || (pm->node.flags & PM_UNSET) /* pm->width is the offset of any subscript */ /* If present, it has to be the end of any chain, see fetchvalue() */ - if (refname && *refname && !pm->width) { - queue_signals(); - if ((hn = (Param)gethashnode2(realparamtab, refname))) { - if ((hn = loadparamnode(paramtab, upscope(hn, pm), refname)) && - hn != stop && !(hn->node.flags & PM_UNSET)) { - /* user can't tag a nameref, safe for loop detection */ - pm->node.flags |= PM_TAGGED; - hn = resolve_nameref_rec(hn, stop, keep_lastref); - pm->node.flags &= ~PM_TAGGED; - } - } else if (keep_lastref) - hn = pm; - unqueue_signals(); - } + || pm->width || !(refname = GETREFNAME(pm)) || !*refname) + return pm; + if (pm->node.flags & PM_TAGGED) { + zerr("%s: invalid self reference", pm->node.nam); + return NULL; } - return hn; + queue_signals(); + if ((pm = (Param)gethashnode2(realparamtab, refname))) { + if ((pm = loadparamnode(paramtab, upscope(pm, ref), refname)) && + pm != stop && !(pm->node.flags & PM_UNSET)) { + /* user can't tag a nameref, safe for loop detection */ + ref->node.flags |= PM_TAGGED; + pm = resolve_nameref_rec(pm, stop, keep_lastref); + ref->node.flags &= ~PM_TAGGED; + } + } else if (keep_lastref) + pm = ref; + unqueue_signals(); + return pm; } /**/ @@ -1927,7 +1927,10 @@ struct tieddata { #define PM_DONTIMPORT (1<<22) /* do not import this variable */ #define PM_DECLARED (1<<22) /* explicitly named with typeset */ #define PM_RESTRICTED (1<<23) /* cannot be changed in restricted mode */ -#define PM_UNSET (1<<24) /* has null value */ +#define PM_UNSET (1<<24) /* If PM_DECLARED is also present, parameter + * has null value. Otherwise, parameter was + * unset. + */ #define PM_DEFAULTED (PM_DECLARED|PM_UNSET) #define PM_REMOVABLE (1<<25) /* special can be removed from paramtab */ #define PM_AUTOLOAD (1<<26) /* autoloaded from module */ diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst index e0f7b1879..fb27e7261 100644 --- a/Test/K01nameref.ztst +++ b/Test/K01nameref.ztst @@ -132,6 +132,68 @@ echo "$0: ref1=$ref1 ref2=$ref2 ref3=$ref3"; } + # Helper function to test assigning and typesetting named references + # in different contexts. + # + # Runs the code passed in "$1" for all combinations of the following + # variables: + # + # - K: Kind of target reference. The assigned/typeset reference is + # one of the following kind: + # - "i": unInitialized reference: + # e.g., "ref0" in "typeset -n ref0" + # - "s": unSet reference: + # e.g., "ref0" in "typeset -n ref0; unset ref0" + # - "d": reference to not-yet-Defined variable: + # e.g., "ref0" in "typeset -n ref0=var" where "var" isn't defined + # + # - G: Global vs local references + # - "-g": All test variables/references are global ones. + # - "" : All test variables/references are local ones. + # + # - N: direct vs indirect references + # - "0": The target reference "ref0" is directly assigned/typeset. + # - "1": The target reference "ref0" is assigned/typeset via a + # named reference "ref1" that refers to "ref0". + test-setting-ref() { + local K G N; + for K in i s d; do + for G in -g ""; do + for N in 0 1; do + test-setting-ref-aux $1 + done + done + done + unset -n ref0 ref1 var + } + # Helper function for the function test-setting-ref defined above. + # + # Runs the code passed in "$1" for the context specified by the + # variables "K", "G", and "N". + test-setting-ref-aux() { + unset -n ref0 ref1 var + # In several contexts different code paths are involved depending + # on whether the target reference "ref0" is assigned/typeset + # directly or via a named reference that refers to it. However, it + # doesn't seem to make a difference whether the chain of references + # is short or long. Therefore only chains of one reference are + # tested. One can manually double check that longer chains work the + # same by replacing the following line by one of the commented one + # just below and checking that all tests still pass. + ((N < 1)) || typeset $G -n ref1=ref0 + # ((N < 1)) || typeset $G -n refX=ref0 ref1=refX + # ((N < 1)) || typeset $G -n refX=ref0 refY=refX ref1=refY + local kind print + case $K in + i) kind="uninitialized reference" ; check=ref0; typeset $G -n ref0;; + s) kind="unset reference" ; check=ref0; typeset $G -n ref0; unset -n ref0;; + d) kind="reference to not-yet-defined"; check=var ; typeset $G -n ref0=var;; + *) echo "Unrecognized case: $case"; return;; + esac + echo "# $K:$kind - ${${G:-local}/-g/global} - ref$N" + { eval ${(e)1}; typeset -p $check } always { TRY_BLOCK_ERROR=0 } 2>&1 + } + %test typeset -n ptr @@ -187,11 +249,13 @@ >typeset -n ptr=gvar typeset -n ptr + typeset -p ptr typeset -t ptr typeset -p ptr 0:change type of a placeholder F:Other type changes are fatal errors, should this also be? ->typeset -n ptr='' +>typeset -n ptr +>typeset -n ptr *?*ptr: can't change type of a named reference typeset -n ptr=var @@ -1659,4 +1723,166 @@ F:converting from association/array to string should work here too 1:self reference via upper reference ?(anon):3: ref1: invalid self reference + # The call to "test-setting-ref 'ref$N=str'" expands to the + # equivalent of the following: + # + # # Test uninitialized references + # typeset -g -n ref0 ; ref0=str; typeset -p ref0; unset -n ref0 + # typeset -g -n ref0 ref1=ref0; ref1=str; typeset -p ref0; unset -n ref0 ref1 + # typeset -n ref0 ; ref0=str; typeset -p ref0; unset -n ref0 + # typeset -n ref0 ref1=ref0; ref1=str; typeset -p ref0; unset -n ref0 ref1 + # # Test unset references + # typeset -g -n ref0 ; unset -n ref0; ref0=str; typeset -p ref0; unset -n ref0 + # ... + # # Test references to not-yet-defined variables + # typeset -g -n ref0=var ; ref0=str; typeset -p var ; unset -n ref0 var + # ... + test-setting-ref 'ref$N=str' +0:assign not fully defined reference with string +># i:uninitialized reference - global - ref0 +>typeset -g -n ref0=str +># i:uninitialized reference - global - ref1 +>typeset -g -n ref0=str +># i:uninitialized reference - local - ref0 +>typeset -n ref0=str +># i:uninitialized reference - local - ref1 +>typeset -n ref0=str +># s:unset reference - global - ref0 +>typeset -g ref0=str +># s:unset reference - global - ref1 +>typeset -g ref0=str +># s:unset reference - local - ref0 +>typeset ref0=str +># s:unset reference - local - ref1 +>typeset ref0=str +># d:reference to not-yet-defined - global - ref0 +>typeset -g var=str +># d:reference to not-yet-defined - global - ref1 +>typeset -g var=str +># d:reference to not-yet-defined - local - ref0 +>typeset -g var=str +># d:reference to not-yet-defined - local - ref1 +>typeset -g var=str + + test-setting-ref 'ref$N=( foo bar )' +0:assign not fully defined reference with array +># i:uninitialized reference - global - ref0 +>(eval):1: ref0: can't change type of a named reference +>typeset -g -n ref0 +># i:uninitialized reference - global - ref1 +>(eval):1: ref1: can't change type of a named reference +>typeset -g -n ref0 +># i:uninitialized reference - local - ref0 +>(eval):1: ref0: can't change type of a named reference +>typeset -n ref0 +># i:uninitialized reference - local - ref1 +>(eval):1: ref1: can't change type of a named reference +>typeset -n ref0 +># s:unset reference - global - ref0 +>typeset -g -a ref0=( foo bar ) +># s:unset reference - global - ref1 +>typeset -g -a ref0=( foo bar ) +># s:unset reference - local - ref0 +>typeset -a ref0=( foo bar ) +># s:unset reference - local - ref1 +>typeset -a ref0=( foo bar ) +># d:reference to not-yet-defined - global - ref0 +>typeset -g -a var=( foo bar ) +># d:reference to not-yet-defined - global - ref1 +>typeset -g -a var=( foo bar ) +># d:reference to not-yet-defined - local - ref0 +>typeset -g -a var=( foo bar ) +># d:reference to not-yet-defined - local - ref1 +>typeset -g -a var=( foo bar ) + + test-setting-ref 'typeset $G ref$N=str' +0:typeset not fully defined reference with string +># i:uninitialized reference - global - ref0 +>typeset -g -n ref0=str +># i:uninitialized reference - global - ref1 +>typeset -g -n ref0=str +># i:uninitialized reference - local - ref0 +>typeset -n ref0=str +># i:uninitialized reference - local - ref1 +>typeset -n ref0=str +># s:unset reference - global - ref0 +>typeset -g ref0=str +># s:unset reference - global - ref1 +>typeset -g ref0=str +># s:unset reference - local - ref0 +>typeset ref0=str +># s:unset reference - local - ref1 +>typeset ref0=str +># d:reference to not-yet-defined - global - ref0 +>typeset -g var=str +># d:reference to not-yet-defined - global - ref1 +>typeset -g var=str +># d:reference to not-yet-defined - local - ref0 +>typeset var=str +># d:reference to not-yet-defined - local - ref1 +>typeset var=str + + test-setting-ref 'typeset $G -a ref$N=( foo bar )' +0:typeset not fully defined reference with array +># i:uninitialized reference - global - ref0 +>(eval):typeset:1: ref0: can't change type of a named reference +>typeset -g -n ref0 +># i:uninitialized reference - global - ref1 +>(eval):typeset:1: ref0: can't change type of a named reference +>typeset -g -n ref0 +># i:uninitialized reference - local - ref0 +>(eval):typeset:1: ref0: can't change type of a named reference +>typeset -n ref0 +># i:uninitialized reference - local - ref1 +>(eval):typeset:1: ref0: can't change type of a named reference +>typeset -n ref0 +># s:unset reference - global - ref0 +>typeset -g -a ref0=( foo bar ) +># s:unset reference - global - ref1 +>typeset -g -a ref0=( foo bar ) +># s:unset reference - local - ref0 +>typeset -a ref0=( foo bar ) +># s:unset reference - local - ref1 +>typeset -a ref0=( foo bar ) +># d:reference to not-yet-defined - global - ref0 +>typeset -g -a var=( foo bar ) +># d:reference to not-yet-defined - global - ref1 +>typeset -g -a var=( foo bar ) +># d:reference to not-yet-defined - local - ref0 +>typeset -a var=( foo bar ) +># d:reference to not-yet-defined - local - ref1 +>typeset -a var=( foo bar ) + + typeset i42=42 + test-setting-ref 'typeset $G -i ref$N=i42' +0:typeset not fully defined reference with integer +># i:uninitialized reference - global - ref0 +>(eval):typeset:1: ref0: can't change type of a named reference +>typeset -g -n ref0 +># i:uninitialized reference - global - ref1 +>(eval):typeset:1: ref0: can't change type of a named reference +>typeset -g -n ref0 +># i:uninitialized reference - local - ref0 +>(eval):typeset:1: ref0: can't change type of a named reference +>typeset -n ref0 +># i:uninitialized reference - local - ref1 +>(eval):typeset:1: ref0: can't change type of a named reference +>typeset -n ref0 +># s:unset reference - global - ref0 +>typeset -g -i ref0=42 +># s:unset reference - global - ref1 +>typeset -g -i ref0=42 +># s:unset reference - local - ref0 +>typeset -i ref0=42 +># s:unset reference - local - ref1 +>typeset -i ref0=42 +># d:reference to not-yet-defined - global - ref0 +>typeset -g -i var=42 +># d:reference to not-yet-defined - global - ref1 +>typeset -g -i var=42 +># d:reference to not-yet-defined - local - ref0 +>typeset -i var=42 +># d:reference to not-yet-defined - local - ref1 +>typeset -i var=42 + %clean diff --git a/Test/V10private.ztst b/Test/V10private.ztst index 4abbbf5c9..256844be1 100644 --- a/Test/V10private.ztst +++ b/Test/V10private.ztst @@ -370,7 +370,7 @@ F:See K01nameref.ztst up-reference part 5 F:Here ptr1 finds private ptr2 by scope mismatch >typeset -n ptr1=ptr2 >typeset -hn ptr2 -*?*read-only variable: ptr2 +?(anon):1: read-only variable: ptr2 () { typeset -n ptr1=ptr2 @@ -378,7 +378,7 @@ F:Here ptr1 finds private ptr2 by scope mismatch typeset -p ptr1 ptr2 typeset val=LOCAL () { - ptr1=val || print -u2 ptr1: assignment failed + ptr1=val # Test dies here as ptr2 is private and uninitialized typeset -n printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2" } @@ -390,13 +390,7 @@ F:See K01nameref.ztst up-reference part 5 F:Here ptr1 finds private ptr2 by scope mismatch >typeset -n ptr1=ptr2 >typeset -hn ptr2='' ->ptr1=ptr2 ->ptr1= ->ptr2= ->typeset -n ptr1=ptr2 ->typeset -hn ptr2='' -*?*ptr1: assignment failed -*?*no such variable: ptr2 +?(anon):1: ptr1: can't modify read-only parameter typeset ptr2 () { |
