From 36886b5bac75cda19506ba357136200ccf2fe2d4 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 28 Nov 2020 09:23:10 -0800 Subject: [PATCH 01/16] 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 a26b2d05b..b9b21f218 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 From a8f2a5d0c868e2a2263bbfaf98713f43477ba896 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 28 Nov 2020 11:25:23 -0800 Subject: [PATCH 02/16] Choose a better bit-value for PM_DECLARED --- Src/zsh.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/zsh.h b/Src/zsh.h index b9b21f218..787afaafa 100644 --- a/Src/zsh.h +++ b/Src/zsh.h @@ -1929,9 +1929,9 @@ struct tieddata { made read-only by the user */ #define PM_READONLY_SPECIAL (PM_SPECIAL|PM_READONLY|PM_RO_BY_DESIGN) #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_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 */ From 128035c93c5bc149e9c3babd48c8c145609e29e0 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Thu, 3 Dec 2020 22:30:04 -0800 Subject: [PATCH 03/16] Partial fix for handling of tied arrays. As of this commit when a tied array is declared but neither the scalar nor the array has an initializer, the array is initialized to empty. The scalar struct param of a tied pair stores a direct pointer to the internal array value of the array struct param, and upon assignment modifies it without referring to the containing struct. This means that there's no opportunity to clear the PM_DECLAREDNULL bits on both structs when the scalar is assigned. Conversely, assigning to the array does use the struct for the scalar. --- Src/builtin.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Src/builtin.c b/Src/builtin.c index 691734221..68ebead7e 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2890,6 +2890,8 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) * * Don't attempt to set it yet, it's too early * to be exported properly. + * + * This creates the array with PM_DECLAREDNULL. */ asg2.name = asg->name; asg2.flags = 0; @@ -2933,6 +2935,10 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) assignaparam(asg->name, zlinklist2array(asg->value.array, 1), flags); } else if (oldval) assignsparam(asg0.name, oldval, 0); + else /*if (asg0.value.scalar)*/ { + /* We have to undo what we did wrong with asg2 */ + apm->node.flags &= ~PM_DECLAREDNULL; + } unqueue_signals(); return 0; From 7a2d6589ba5e088b959657e35fee867d3d72e55b Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Fri, 4 Dec 2020 14:46:19 -0800 Subject: [PATCH 04/16] Unset of a tied local scalar previously left it using tiedarr_gsu --- Src/params.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Src/params.c b/Src/params.c index a0d4caf52..1a047d9e0 100644 --- a/Src/params.c +++ b/Src/params.c @@ -3659,6 +3659,8 @@ unsetparam_pm(Param pm, int altflag, int exp) } zsfree(altremove); + if (!(pm->node.flags & PM_SPECIAL)) + pm->gsu.s = &stdscalar_gsu; } /* From fa5f59bf710099dcaa8f22729591c2580c052db9 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Fri, 4 Dec 2020 15:09:14 -0800 Subject: [PATCH 05/16] Additional tied-array cleanup when tied scalar had a previous value --- Src/builtin.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Src/builtin.c b/Src/builtin.c index 68ebead7e..fff0b641f 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2933,11 +2933,11 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) if (asg->value.array) { int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0; assignaparam(asg->name, zlinklist2array(asg->value.array, 1), flags); - } else if (oldval) - assignsparam(asg0.name, oldval, 0); - else /*if (asg0.value.scalar)*/ { + } else /*if (asg0.value.scalar || oldval)*/ { /* We have to undo what we did wrong with asg2 */ apm->node.flags &= ~PM_DECLAREDNULL; + if (oldval) + assignsparam(asg0.name, oldval, 0); } unqueue_signals(); From b1bd99b67f1a2e09dff651f102249c441529d530 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Fri, 4 Dec 2020 16:14:28 -0800 Subject: [PATCH 06/16] Final repairs for declared state of tied arrays Fixups still required in bin_typeset, but assignments to scalar work. --- Src/builtin.c | 4 ++-- Src/params.c | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Src/builtin.c b/Src/builtin.c index fff0b641f..8d8ff68e0 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2837,7 +2837,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) unqueue_signals(); return 1; } else if (pm) { - if (!(pm->node.flags & PM_UNSET) + if ((!(pm->node.flags & PM_UNSET) || pm->node.flags & PM_DECLARED) && (locallevel == pm->level || !(on & PM_LOCAL))) { if (pm->node.flags & PM_TIED) { if (PM_TYPE(pm->node.flags) != PM_SCALAR) { @@ -2933,7 +2933,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) if (asg->value.array) { int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0; assignaparam(asg->name, zlinklist2array(asg->value.array, 1), flags); - } else /*if (asg0.value.scalar || oldval)*/ { + } else if (asg0.value.scalar || oldval) { /* We have to undo what we did wrong with asg2 */ apm->node.flags &= ~PM_DECLAREDNULL; if (oldval) diff --git a/Src/params.c b/Src/params.c index 1a047d9e0..c09a3eccf 100644 --- a/Src/params.c +++ b/Src/params.c @@ -4125,6 +4125,11 @@ tiedarrsetfn(Param pm, char *x) if (*dptr->arrptr) freearray(*dptr->arrptr); + else if (pm->ename) { + Param altpm = (Param) paramtab->getnode(paramtab, pm->ename); + if (altpm) + altpm->node.flags &= ~PM_DECLAREDNULL; + } if (x) { char sepbuf[3]; if (imeta(dptr->joinchar)) From d4fa4c36896e0d89bf4b32388db08fc1a8523dd4 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Wed, 9 Dec 2020 21:32:36 -0800 Subject: [PATCH 07/16] Make DECLAREDNULL require POSIXBUILTINS and introduce a new test file. V10private has been made agnostic to POSIXBUILTINS. --- Test/E03posix.ztst | 105 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 Test/E03posix.ztst diff --git a/Test/E03posix.ztst b/Test/E03posix.ztst new file mode 100644 index 000000000..5e6eddeba --- /dev/null +++ b/Test/E03posix.ztst @@ -0,0 +1,105 @@ +# Test POSIX-specific behavior +# Currently this covers only POSIXBUILTINS, other behaviors are in their +# more directly related sections +# + +%prep + setopt POSIX_BUILTINS + +%test + + local parentenv=preserved + fn() { + typeset -h +g -m \* + unset -m \* + integer i=9 + float -H f=9 + declare -t scalar + declare -H -a array + typeset + typeset + + } + fn + echo $parentenv +0:Parameter hiding and tagging, printing types and values +>array local array +>float local f +>integer local i=9 +>local tagged scalar +>array local array +>float local f +>integer local i +>local tagged scalar +>preserved + + readonly foo=bar novalue + readonly -p +0:readonly -p output (no readonly specials) +>readonly foo=bar +>readonly novalue + + local -a myarray + typeset -p1 myarray + myarray=("&" sand '""' "" plugh) + typeset -p1 myarray +0:typeset -p1 output for array +>typeset -a myarray +>typeset -a myarray=( +> '&' +> sand +> '""' +> '' +> plugh +>) + + local -A myhash + typeset -p1 myhash + myhash=([one]=two [three]= [four]="[]") + typeset -p1 myhash +0:typeset -p1 output for associative array +>typeset -A myhash +>typeset -A myhash=( +> [four]='[]' +> [one]=two +> [three]='' +>) + + str=s + arr=(a) + typeset -A ass + ass=(a a) + integer i=0 + float f=0 + print ${(t)str} ${(t)arr} ${(t)ass} ${(t)i} ${(t)f} +0:${(t)...} +>scalar array association-local integer-local float-local + + print $empty[(i)] $empty[(I)] +0:(i) and (I) return nothing for empty array +> + + ( + # reserved words are handled during parsing, + # hence eval... + disable -r typeset + eval ' + setopt kshtypeset + ktvars=(ktv1 ktv2) + typeset ktfoo=`echo arg1 arg2` $ktvars + () { + local ktfoo + print $+ktv1 $+ktv2 $+ktv3 $+ktfoo + } + print $ktfoo + unsetopt kshtypeset + typeset noktfoo=`echo noktarg1 noktarg2` + print $noktfoo + print $+noktarg1 $+noktarg2 + unset ktfoo ktv1 ktv2 noktfoo noktarg2 + ' + ) +0:KSH_TYPESET option +>0 0 0 0 +>arg1 arg2 +>noktarg1 +>0 0 From 2eb9289d1277bf71b1b9dea2addbddd36b61f276 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Thu, 17 Dec 2020 21:08:47 -0800 Subject: [PATCH 08/16] Missed files from POSXIBUILTINS commit --- Src/builtin.c | 5 +++-- Test/V10private.ztst | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Src/builtin.c b/Src/builtin.c index 8d8ff68e0..b0d4eb7f7 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2491,7 +2491,8 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func), return NULL; } } - pm->node.flags |= PM_DECLAREDNULL; + if (isset(POSIXBUILTINS)) + pm->node.flags |= PM_DECLAREDNULL; } else { if (idigit(*pname)) zerrnam(cname, "not an identifier: %s", pname); @@ -2891,7 +2892,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) * Don't attempt to set it yet, it's too early * to be exported properly. * - * This creates the array with PM_DECLAREDNULL. + * This may create the array with PM_DECLAREDNULL. */ asg2.name = asg->name; asg2.flags = 0; diff --git a/Test/V10private.ztst b/Test/V10private.ztst index a3a63867b..03e8259d5 100644 --- a/Test/V10private.ztst +++ b/Test/V10private.ztst @@ -19,14 +19,14 @@ () { print $scalar_test private scalar_test - print $+scalar_test + typeset +m scalar_test unset scalar_test print $+scalar_test } print $scalar_test 0:basic scope hiding >toplevel ->1 +>local scalar_test >0 >toplevel @@ -45,14 +45,14 @@ print $+unset_test () { private unset_test - print $+unset_test + typeset +m unset_test unset_test=setme print $unset_test } print $+unset_test 0:variable defined only in scope >0 ->1 +>local unset_test >setme >0 @@ -62,13 +62,13 @@ local -Pa array_test=(in function) () { private array_test - print $+array_test + typeset +m array_test } print $array_test } print $array_test 0:nested scope with different type, correctly restored ->1 +>local array_test >in function >top level From d649bf78dc3096d6ca0c705257a928b1acce3b82 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sun, 27 Dec 2020 14:36:35 -0800 Subject: [PATCH 09/16] Fix ${(t)var} output, add comparative test cases --- Src/subst.c | 3 ++- Test/E01options.ztst | 15 +++++++++++++++ Test/E03posix.ztst | 14 ++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Src/subst.c b/Src/subst.c index 96e0914eb..9928be0e9 100644 --- a/Src/subst.c +++ b/Src/subst.c @@ -2563,7 +2563,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, * Handle the (t) flag: value now becomes the type * information for the parameter. */ - if (v && v->pm && !(v->pm->node.flags & PM_UNSET)) { + if (v && v->pm && ((v->pm->node.flags & PM_DECLARED) || + !(v->pm->node.flags & PM_UNSET))) { int f = v->pm->node.flags; switch (PM_TYPE(f)) { diff --git a/Test/E01options.ztst b/Test/E01options.ztst index 415f46cd7..72749e6ab 100644 --- a/Test/E01options.ztst +++ b/Test/E01options.ztst @@ -1451,3 +1451,18 @@ F:If this test fails at the first unsetopt, refer to P01privileged.ztst. 0q:RM_STAR_SILENT *>zsh: sure you want to delete all 15 files in ${PWD:h}/options.tmp \[yn\]\? ${BEL}(|n) *>zsh: sure you want to delete (all <->|more than <->) files in / \[yn\]\? ${BEL}(|n) + + () { + local var + print ${(t)var} + } +0:(t) returns correct type +>scalar-local + + () { + readonly var + typeset -p var + } +0:readonly with typeset -p +F:compare E03posix.ztst +>typeset -r var='' diff --git a/Test/E03posix.ztst b/Test/E03posix.ztst index 5e6eddeba..c59ca4f6e 100644 --- a/Test/E03posix.ztst +++ b/Test/E03posix.ztst @@ -103,3 +103,17 @@ >arg1 arg2 >noktarg1 >0 0 + + () { + local var + print ${(t)var} + } +0:(t) returns correct type +>scalar-local + + () { + readonly var + typeset -p var + } +0:readonly with typeset -p +>typeset -g -r var From fd89e7cefa4d3806f3f469d5ad4b496d130305a6 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 27 Mar 2021 14:04:05 -0700 Subject: [PATCH 10/16] Change DECLAREDNULL to DEFAULTED --- Src/builtin.c | 6 +++--- Src/params.c | 16 ++++++++-------- Src/zsh.h | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Src/builtin.c b/Src/builtin.c index b0d4eb7f7..f0c490119 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2492,7 +2492,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func), } } if (isset(POSIXBUILTINS)) - pm->node.flags |= PM_DECLAREDNULL; + pm->node.flags |= PM_DEFAULTED; } else { if (idigit(*pname)) zerrnam(cname, "not an identifier: %s", pname); @@ -2892,7 +2892,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) * Don't attempt to set it yet, it's too early * to be exported properly. * - * This may create the array with PM_DECLAREDNULL. + * This may create the array with PM_DEFAULTED. */ asg2.name = asg->name; asg2.flags = 0; @@ -2936,7 +2936,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) assignaparam(asg->name, zlinklist2array(asg->value.array, 1), flags); } else if (asg0.value.scalar || oldval) { /* We have to undo what we did wrong with asg2 */ - apm->node.flags &= ~PM_DECLAREDNULL; + apm->node.flags &= ~PM_DEFAULTED; if (oldval) assignsparam(asg0.name, oldval, 0); } diff --git a/Src/params.c b/Src/params.c index c09a3eccf..33bbc54f6 100644 --- a/Src/params.c +++ b/Src/params.c @@ -3056,7 +3056,7 @@ assignsparam(char *s, char *val, int flags) * Don't warn about anything. */ flags &= ~ASSPM_WARN; - v->pm->node.flags &= ~PM_DECLAREDNULL; + v->pm->node.flags &= ~PM_DEFAULTED; } *ss = '['; v = NULL; @@ -3082,7 +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; + v->pm->node.flags &= ~PM_DEFAULTED; if (flags & ASSPM_AUGMENT) { if (v->start == 0 && v->end == -1) { switch (PM_TYPE(v->pm->node.flags)) { @@ -3235,7 +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; + v->pm->node.flags &= ~PM_DEFAULTED; /* * At this point, we may have array entries consisting of @@ -3448,7 +3448,7 @@ sethparam(char *s, char **val) return NULL; } check_warn_pm(v->pm, "associative array", checkcreate, 1); - v->pm->node.flags &= ~PM_DECLAREDNULL; + v->pm->node.flags &= ~PM_DEFAULTED; setarrvalue(v, val); unqueue_signals(); return v->pm; @@ -3520,7 +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; + v->pm->node.flags &= ~PM_DEFAULTED; setnumvalue(v, val); unqueue_signals(); return v->pm; @@ -4128,7 +4128,7 @@ tiedarrsetfn(Param pm, char *x) else if (pm->ename) { Param altpm = (Param) paramtab->getnode(paramtab, pm->ename); if (altpm) - altpm->node.flags &= ~PM_DECLAREDNULL; + altpm->node.flags &= ~PM_DEFAULTED; } if (x) { char sepbuf[3]; @@ -5049,7 +5049,7 @@ arrfixenv(char *s, char **t) if (isset(ALLEXPORT)) pm->node.flags |= PM_EXPORTED; - pm->node.flags &= ~PM_DECLAREDNULL; + pm->node.flags &= ~PM_DEFAULTED; /* * Do not "fix" parameters that were not exported @@ -5856,7 +5856,7 @@ printparamnode(HashNode hn, int printflags) if (p->node.flags & PM_UNSET) { if ((printflags & (PRINT_POSIX_READONLY|PRINT_POSIX_EXPORT) && p->node.flags & (PM_READONLY|PM_EXPORTED)) || - (p->node.flags & PM_DECLAREDNULL) == PM_DECLAREDNULL) { + (p->node.flags & PM_DEFAULTED) == PM_DEFAULTED) { /* * 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 787afaafa..27be1f788 100644 --- a/Src/zsh.h +++ b/Src/zsh.h @@ -1932,7 +1932,7 @@ struct tieddata { #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_DECLAREDNULL (PM_DECLARED|PM_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 */ #define PM_NORESTORE (1<<27) /* do not restore value of local special */ From 13f85796ac15c871cf760f6415424f4f96c0a460 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 10 Apr 2021 12:22:43 -0700 Subject: [PATCH 11/16] unposted (cf. 48469): document oddness of ${emptystr[(i)]}. --- Doc/Zsh/params.yo | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index 36c1ae4c2..a9044336f 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -393,6 +393,11 @@ is compared to the pattern, and the first matching key found is the result. On failure substitutes the length of the array plus one, as discussed under the description of `tt(r)', or the empty string for an associative array. + +Note: Although `tt(i)' may be applied to a scalar substitution to find +the offset of a substring, the results are likely to be misleading when +searching within substitutions that yield an empty string, or when +searching for the empty substring. ) item(tt(I))( Like `tt(i)', but gives the index of the last match, or all possible From e67ccd7f1efae7696dc17f6e3e720cd994e90155 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 10 Apr 2021 14:10:21 -0700 Subject: [PATCH 12/16] Add TYPESET_DOES_NOT_SET option (cf. 48469) --- Src/builtin.c | 2 +- Src/options.c | 1 + Src/zsh.h | 1 + Test/E03posix.ztst | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Src/builtin.c b/Src/builtin.c index f0c490119..edd4cad44 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2491,7 +2491,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func), return NULL; } } - if (isset(POSIXBUILTINS)) + if (isset(TYPESETDOESNOTSET)) pm->node.flags |= PM_DEFAULTED; } else { if (idigit(*pname)) diff --git a/Src/options.c b/Src/options.c index fba021e7d..766ffdfdb 100644 --- a/Src/options.c +++ b/Src/options.c @@ -257,6 +257,7 @@ static struct optname optns[] = { {{NULL, "sunkeyboardhack", 0}, SUNKEYBOARDHACK}, {{NULL, "transientrprompt", 0}, TRANSIENTRPROMPT}, {{NULL, "trapsasync", 0}, TRAPSASYNC}, +{{NULL, "typesetdoesnotset", OPT_EMULATE|OPT_BOURNE}, TYPESETDOESNOTSET}, {{NULL, "typesetsilent", OPT_EMULATE|OPT_BOURNE}, TYPESETSILENT}, {{NULL, "unset", OPT_EMULATE|OPT_BSHELL}, UNSET}, {{NULL, "verbose", 0}, VERBOSE}, diff --git a/Src/zsh.h b/Src/zsh.h index 27be1f788..12efb784f 100644 --- a/Src/zsh.h +++ b/Src/zsh.h @@ -2536,6 +2536,7 @@ enum { SUNKEYBOARDHACK, TRANSIENTRPROMPT, TRAPSASYNC, + TYPESETDOESNOTSET, TYPESETSILENT, UNSET, VERBOSE, diff --git a/Test/E03posix.ztst b/Test/E03posix.ztst index c59ca4f6e..a2769f3aa 100644 --- a/Test/E03posix.ztst +++ b/Test/E03posix.ztst @@ -4,7 +4,7 @@ # %prep - setopt POSIX_BUILTINS + setopt POSIX_BUILTINS TYPESET_DOES_NOT_SET %test From b3613e4895f7b059558c4ef211189b516dbf903d Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Mon, 12 Apr 2021 13:59:06 -0700 Subject: [PATCH 13/16] Change TYPESET_DOES_NOT_SET to TYPESET_TO_UNSET to avoid double-negative --- Doc/Zsh/builtins.yo | 6 +++++- Doc/Zsh/options.yo | 10 ++++++++++ Src/builtin.c | 2 +- Src/options.c | 2 +- Src/zsh.h | 2 +- Test/D06subscript.ztst | 5 +++++ Test/E03posix.ztst | 45 +++++++++++++++++++++++++++++++++++++++++- 7 files changed, 67 insertions(+), 5 deletions(-) diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index a7afe42cf..61dc6986f 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -1872,7 +1872,11 @@ ifnzman(noderef(Local Parameters))\ retain their special attributes when made local. For each var(name)tt(=)var(value) assignment, the parameter -var(name) is set to var(value). +var(name) is set to var(value). If the assignment is omitted and var(name) +does em(not) refer to an existing parameter, a new parameter is intialized +to empty string, zero, or empty array (as appropriate), em(unless) the +shell option tt(TYPESET_TO_UNSET) is set. When that option is set, +the parameter attributes are recorded but the parameter remains unset. If the shell option tt(TYPESET_SILENT) is not set, for each remaining var(name) that refers to a parameter that is already set, the name and diff --git a/Doc/Zsh/options.yo b/Doc/Zsh/options.yo index b3bf11f5c..6a1f82b07 100644 --- a/Doc/Zsh/options.yo +++ b/Doc/Zsh/options.yo @@ -1926,6 +1926,16 @@ If the option is set, they will only be shown when parameters are selected with the `tt(-m)' option. The option `tt(-p)' is available whether or not the option is set. ) +pindex(TYPESET_TO_UNSET) +pindex(NO_TYPESET_TO_UNSET) +pindex(TYPESETTOUNSET) +pindex(NOTYPESETTOUNSET) +item(tt(TYPESET_TO_UNSET) )( +When declaring a new parameter with any of the `tt(typeset)' family of +related commands, the parameter remains unset unless and until a +value is explicity assigned to it, either in the `tt(typeset)' command +itself or as a later assignment statement. +) pindex(VERBOSE) pindex(NO_VERBOSE) pindex(NOVERBOSE) diff --git a/Src/builtin.c b/Src/builtin.c index edd4cad44..6d119f7a5 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2491,7 +2491,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func), return NULL; } } - if (isset(TYPESETDOESNOTSET)) + if (isset(TYPESETTOUNSET)) pm->node.flags |= PM_DEFAULTED; } else { if (idigit(*pname)) diff --git a/Src/options.c b/Src/options.c index 766ffdfdb..23935ae18 100644 --- a/Src/options.c +++ b/Src/options.c @@ -257,8 +257,8 @@ static struct optname optns[] = { {{NULL, "sunkeyboardhack", 0}, SUNKEYBOARDHACK}, {{NULL, "transientrprompt", 0}, TRANSIENTRPROMPT}, {{NULL, "trapsasync", 0}, TRAPSASYNC}, -{{NULL, "typesetdoesnotset", OPT_EMULATE|OPT_BOURNE}, TYPESETDOESNOTSET}, {{NULL, "typesetsilent", OPT_EMULATE|OPT_BOURNE}, TYPESETSILENT}, +{{NULL, "typesettounset", OPT_EMULATE|OPT_BOURNE}, TYPESETTOUNSET}, {{NULL, "unset", OPT_EMULATE|OPT_BSHELL}, UNSET}, {{NULL, "verbose", 0}, VERBOSE}, {{NULL, "vi", 0}, VIMODE}, diff --git a/Src/zsh.h b/Src/zsh.h index 12efb784f..490407ad0 100644 --- a/Src/zsh.h +++ b/Src/zsh.h @@ -2536,8 +2536,8 @@ enum { SUNKEYBOARDHACK, TRANSIENTRPROMPT, TRAPSASYNC, - TYPESETDOESNOTSET, TYPESETSILENT, + TYPESETTOUNSET, UNSET, VERBOSE, VIMODE, diff --git a/Test/D06subscript.ztst b/Test/D06subscript.ztst index c1a8d79cf..4225c543c 100644 --- a/Test/D06subscript.ztst +++ b/Test/D06subscript.ztst @@ -289,3 +289,8 @@ F:Regression test for workers/42297 >14 24 >b b >b?rbaz foob?r + + i=1,3 + [[ ${a[$i]} = ${a[i]} ]] +0f:Math evaluation of commas in array subscripts +F:In math, (($i)) should be the same as ((i)). diff --git a/Test/E03posix.ztst b/Test/E03posix.ztst index a2769f3aa..fb394986d 100644 --- a/Test/E03posix.ztst +++ b/Test/E03posix.ztst @@ -4,7 +4,7 @@ # %prep - setopt POSIX_BUILTINS TYPESET_DOES_NOT_SET + setopt POSIX_BUILTINS TYPESET_TO_UNSET %test @@ -117,3 +117,46 @@ } 0:readonly with typeset -p >typeset -g -r var + +# Tests expected to fail + + echo - +0f:A single "-" for echo does not end the arguments +F:POSIX requires a solitary "-" to be a plain argument +>- + + ARGV0=sh $ZTST_testdir/../Src/zsh -c 'foreach() { true; }' +-f:"foreach" is not a reserved word + + ARGV0=sh $ZTST_testdir/../Src/zsh -c 'end() { true; } +-f:"end" is not a reserved word + + a='a:b:' ARGV0=sh $ZTST_testdir/../Src/zsh -c 'IFS=:; printf "<%s>\n" $a' +0f:IFS is a separator, not a delimiter +> +> + + a=$'\ra\r\rb' ARGV0=sh $ZTST_testdir/../Src/zsh -c 'IFS=:; printf "<%s>\n" $a' +0f:All whitespace characters are "IFS whitespace" +F:isspace('\r') is true so \r should behave like space, \t, \n +F:This may also need to apply to multibyte whitespace +> +> + + ARGV0=sh $ZTST_testdir/../Src/zsh -c 'IFS=2; printf "<%s>\n" $((11*11))' +0f:IFS applies to math results (numbers treated as strings) +><1> +><1> + + ARGV0=sh $ZTST_testdir/../Src/zsh -c 'inf=42; echo $((inf))' +0f:The math constant Inf is case-sensitive, with capital I +>42 + + ARGV0=sh $ZTST_testdir/../Src/zsh -c 'EUID=10; echo "$EUID"' +-f:EUID is not a special variable +>10 + + ARGV0=sh $ZTST_testdir/../Src/zsh -c "printf '<%10s>\n' St$'\M-C\M-)'phane" +0f:Width of %s is computed in bytes not characters +F:This is considered a bugfix in zsh +>< Stéphane> From 3811abe01c1f2247fc078230eec205ba7e6b08d2 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Mon, 12 Apr 2021 14:45:48 -0700 Subject: [PATCH 14/16] Completion system assumes typeset does initialize declared variables. --- Completion/compinit | 1 + 1 file changed, 1 insertion(+) diff --git a/Completion/compinit b/Completion/compinit index e81cd1604..1f2e7c634 100644 --- a/Completion/compinit +++ b/Completion/compinit @@ -165,6 +165,7 @@ _comp_options=( NO_posixidentifiers NO_shwordsplit NO_shglob + NO_typesettounset NO_warnnestedvar NO_warncreateglobal ) From 6c9a1f5e80d018e28cf6760cda7ffe18cc5a2df8 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Mon, 12 Apr 2021 15:43:23 -0700 Subject: [PATCH 15/16] Update descriptive test for POSIX $((inf)) failure --- Test/E03posix.ztst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Test/E03posix.ztst b/Test/E03posix.ztst index fb394986d..7db4c0c84 100644 --- a/Test/E03posix.ztst +++ b/Test/E03posix.ztst @@ -149,7 +149,8 @@ F:This may also need to apply to multibyte whitespace ><1> ARGV0=sh $ZTST_testdir/../Src/zsh -c 'inf=42; echo $((inf))' -0f:The math constant Inf is case-sensitive, with capital I +0f:All identifiers are variable references in POSIX arithmetic +F:POSIX has neither math functions nor floating point >42 ARGV0=sh $ZTST_testdir/../Src/zsh -c 'EUID=10; echo "$EUID"' From fefd3854b4abea8b46a2ad568762d3a710af77d9 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Tue, 13 Apr 2021 14:22:11 -0700 Subject: [PATCH 16/16] 48487 (amended): Failure test for math evaluation oddity This was inadvertently included with TYPESET_TO_UNSET, so only the amendment is included here. --- Test/D06subscript.ztst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Test/D06subscript.ztst b/Test/D06subscript.ztst index 4225c543c..adbd398c4 100644 --- a/Test/D06subscript.ztst +++ b/Test/D06subscript.ztst @@ -293,4 +293,4 @@ F:Regression test for workers/42297 i=1,3 [[ ${a[$i]} = ${a[i]} ]] 0f:Math evaluation of commas in array subscripts -F:In math, (($i)) should be the same as ((i)). +F:In math, (($i)) should be the same as ((i)), see workers/47748.