From dddae7f05582c6c8e91501dbea6106636cd76f6c Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 28 Nov 2020 09:23:10 -0800 Subject: Add PM_DECLARED and PM_DECLAREDNULL parameter flags. This addresses the issue that "typeset foo" creates $foo set to an empty string, which differs from typeset handling in bash and ksh. It does this by concealing the internal value rather than alter the way internal values are defaulted. This imposes the following changes: A typeset variable with no assignment triggers NO_UNSET warnings when the name is used in parameter expansion or math. The typeset -AEFHLRTZailux options are applied upon the first assignment to the variable. Explicit unset before the first assignment discards all of those properties. If any option is applied to an existing name, historic behavior is unchanged. Consequent to the foregoing, the (t) parameter expansion flag prints nothing until after the first assignment, and the (i) and (I) subscript flags also print nothing. The bash behavior of "unset foo; typeset -p foo" is NOT used. This is called out as an emulation distinction, not a change. The test cases have not been updated, so several now fail. The test harness has been updated to declare default values. --- Src/builtin.c | 1 + Src/params.c | 15 ++++++++++++--- Src/zsh.h | 2 ++ Test/runtests.zsh | 2 +- Test/ztst.zsh | 2 +- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Src/builtin.c b/Src/builtin.c index 26335a2e8..691734221 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2491,6 +2491,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func), return NULL; } } + pm->node.flags |= PM_DECLAREDNULL; } else { if (idigit(*pname)) zerrnam(cname, "not an identifier: %s", pname); diff --git a/Src/params.c b/Src/params.c index 122f5da7d..a0d4caf52 100644 --- a/Src/params.c +++ b/Src/params.c @@ -2093,7 +2093,8 @@ fetchvalue(Value v, char **pptr, int bracks, int flags) if (sav) *s = sav; *pptr = s; - if (!pm || (pm->node.flags & PM_UNSET)) + if (!pm || ((pm->node.flags & PM_UNSET) && + !(pm->node.flags & PM_DECLARED))) return NULL; if (v) memset(v, 0, sizeof(*v)); @@ -3055,6 +3056,7 @@ assignsparam(char *s, char *val, int flags) * Don't warn about anything. */ flags &= ~ASSPM_WARN; + v->pm->node.flags &= ~PM_DECLAREDNULL; } *ss = '['; v = NULL; @@ -3080,6 +3082,7 @@ assignsparam(char *s, char *val, int flags) } if (flags & ASSPM_WARN) check_warn_pm(v->pm, "scalar", created, 1); + v->pm->node.flags &= ~PM_DECLAREDNULL; if (flags & ASSPM_AUGMENT) { if (v->start == 0 && v->end == -1) { switch (PM_TYPE(v->pm->node.flags)) { @@ -3232,6 +3235,7 @@ assignaparam(char *s, char **val, int flags) if (flags & ASSPM_WARN) check_warn_pm(v->pm, "array", created, may_warn_about_nested_vars); + v->pm->node.flags &= ~PM_DECLAREDNULL; /* * At this point, we may have array entries consisting of @@ -3444,6 +3448,7 @@ sethparam(char *s, char **val) return NULL; } check_warn_pm(v->pm, "associative array", checkcreate, 1); + v->pm->node.flags &= ~PM_DECLAREDNULL; setarrvalue(v, val); unqueue_signals(); return v->pm; @@ -3515,6 +3520,7 @@ assignnparam(char *s, mnumber val, int flags) if (flags & ASSPM_WARN) check_warn_pm(v->pm, "numeric", 0, 1); } + v->pm->node.flags &= ~PM_DECLAREDNULL; setnumvalue(v, val); unqueue_signals(); return v->pm; @@ -3619,6 +3625,7 @@ unsetparam_pm(Param pm, int altflag, int exp) else altremove = NULL; + pm->node.flags &= ~PM_DECLARED; /* like ksh, not like bash */ if (!(pm->node.flags & PM_UNSET)) pm->gsu.s->unsetfn(pm, exp); if (pm->env) @@ -5035,6 +5042,7 @@ arrfixenv(char *s, char **t) if (isset(ALLEXPORT)) pm->node.flags |= PM_EXPORTED; + pm->node.flags &= ~PM_DECLAREDNULL; /* * Do not "fix" parameters that were not exported @@ -5839,8 +5847,9 @@ printparamnode(HashNode hn, int printflags) Param peer = NULL; if (p->node.flags & PM_UNSET) { - if (printflags & (PRINT_POSIX_READONLY|PRINT_POSIX_EXPORT) && - p->node.flags & (PM_READONLY|PM_EXPORTED)) { + if ((printflags & (PRINT_POSIX_READONLY|PRINT_POSIX_EXPORT) && + p->node.flags & (PM_READONLY|PM_EXPORTED)) || + (p->node.flags & PM_DECLAREDNULL) == PM_DECLAREDNULL) { /* * Special POSIX rules: show the parameter as readonly/exported * even though it's unset, but with no value. diff --git a/Src/zsh.h b/Src/zsh.h index d70a4017c..e98f3e328 100644 --- a/Src/zsh.h +++ b/Src/zsh.h @@ -1931,6 +1931,8 @@ struct tieddata { #define PM_DONTIMPORT (1<<22) /* do not import this variable */ #define PM_RESTRICTED (1<<23) /* cannot be changed in restricted mode */ #define PM_UNSET (1<<24) /* has null value */ +#define PM_DECLARED (1<<17) /* */ +#define PM_DECLAREDNULL (PM_DECLARED|PM_UNSET) #define PM_REMOVABLE (1<<25) /* special can be removed from paramtab */ #define PM_AUTOLOAD (1<<26) /* autoloaded from module */ #define PM_NORESTORE (1<<27) /* do not restore value of local special */ diff --git a/Test/runtests.zsh b/Test/runtests.zsh index 562234d91..b66d579b6 100644 --- a/Test/runtests.zsh +++ b/Test/runtests.zsh @@ -7,7 +7,7 @@ emulate zsh # protect from catastrophic failure of an individual test. # We could probably do that with subshells instead. -integer success failure skipped retval +integer success=0 failure=0 skipped=0 retval for file in "${(f)ZTST_testlist}"; do $ZTST_exe +Z -f $ZTST_srcdir/ztst.zsh $file retval=$? diff --git a/Test/ztst.zsh b/Test/ztst.zsh index e668ae942..a59c06dcf 100755 --- a/Test/ztst.zsh +++ b/Test/ztst.zsh @@ -60,7 +60,7 @@ ZTST_mainopts=(${(kv)options}) ZTST_testdir=$PWD ZTST_testname=$1 -integer ZTST_testfailed +integer ZTST_testfailed=0 # This is POSIX nonsense. Because of the vague feeling someone, somewhere # may one day need to examine the arguments of "tail" using a standard -- cgit 1.4.1