about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog28
-rw-r--r--Doc/Zsh/params.yo23
-rw-r--r--NEWS15
-rw-r--r--README11
-rw-r--r--Src/Zle/zle_tricky.c2
-rw-r--r--Src/exec.c86
-rw-r--r--Src/params.c192
-rw-r--r--Src/subst.c71
-rw-r--r--Src/zsh.h20
-rw-r--r--Test/B02typeset.ztst15
-rw-r--r--Test/D04parameter.ztst27
11 files changed, 325 insertions, 165 deletions
diff --git a/ChangeLog b/ChangeLog
index 464837cd6..2c5e0001c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2017-09-24  Peter Stephenson  <p.w.stephenson@ntlworld.com>
+
+	* 41754: Doc/Zsh/params.yo, NEWS,  README,
+	Src/Zle/zle_tricky.c, Src/exec.c, Src/params.c, Src/subst.c,
+	Src/zsh.h, Test/B02typeset.ztst, Test/D04parameter.ztst:
+	allow mix of [ind]=val and traditional assignment for normal
+	(but not associative) arrays, add tests.
+
 2017-09-22  Oliver Kiddle  <opk@zsh.org>
 
 	* 41742: Completion/Unix/Command/_mtr: update for mtr 0.92
@@ -2305,7 +2313,7 @@
 2016-10-24  Barton E. Schaefer  <schaefer@zsh.org>
 
 	* unposted: NEWS, README: update for 39704.
-	
+
 	* 39704: Src/params.c, Test/B02typeset.ztst, Test/B03print.ztst,
 	Test/V10private.ztst: the output of "typeset -p" uses "export"
 	commands or the "-g" option for parameters that are not local to
@@ -3363,7 +3371,7 @@
 2016-07-17  Barton E. Schaefer  <schaefer@zsh.org>
 
 	* unposted: Functions/Misc/add-zle-hook-widget:  Move from Zle/.
-	
+
 	* 38866: Doc/Zsh/contrib.yo: update add-zle-hook-widget for 38850.
 
 	* 38866 (+ tweak 38872): Functions/Zle/add-zle-hook-widget: fix
@@ -4122,7 +4130,7 @@
 	* 37971 (cf. users/21284: Eric Freese):
 	Functions/Zle/bracketed-paste-magic: fix potential issues when
 	interacting with user-defined widgets
-	
+
 	* 37961: Src/Zle/complist.c: in interactive menuselection, use of
 	"compadd -x" (e.g. the "warnings" zstyle) may have replaced the
 	completion list, so skip highlighting of the current selection
@@ -5263,7 +5271,7 @@
 2015-09-30  Barton E. Schaefer  <schaefer@zsh.org>
 
 	* users/20672: Src/text.c: missing "do" in gettext2() for "select"
-	
+
 	* 36707: Src/exec.c, Src/loop.c: distinguish ERR_RETURN value
 	of retflag so that execif() can ignore it in the test sublist
 
@@ -5347,7 +5355,7 @@
 2015-09-28  Jun-ichi Takimoto <takimoto-j@kba.biglobe.ne.jp>
 
 	* 36631: Completion/Unix/Command/_sh,
-	Completion/Unix/Command/_zsh: separate _zsh from _sh	
+	Completion/Unix/Command/_zsh: separate _zsh from _sh
 
 2015-09-26  Barton E. Schaefer  <schaefer@zsh.org>
 
@@ -6349,7 +6357,7 @@
 2015-07-22  Barton E. Schaefer  <schaefer@zsh.org>
 
 	* unposted: Test/B02typeset.ztst: fix another test for 35581
-	
+
 	* 35582: Test/A06assign.ztst, Test/B02typeset.ztst: test for 35581
 
 	* 35581: Src/params.c: output array assignments with spaces inside
@@ -6708,7 +6716,7 @@
 	occur anywhere in a coredump filename
 
 	* 35476: Src/params.c: Allow setting $0 when POSIX_ARGZERO is
-	not set	
+	not set
 
 2015-06-16  Barton E. Schaefer  <schaefer@zsh.org>
 
@@ -7952,7 +7960,7 @@
 	tty_poll().
 
 	* 34369: Daniel Shahaf: document error / warning codes.
-	
+
 	* 34383: Src/utils.c: new ztrdup() shoud be dupstring().
 
 2015-01-25  Oliver Kiddle  <opk@zsh.org>
@@ -8008,7 +8016,7 @@
 2015-01-22  Barton E. Schaefer  <schaefer@zsh.org>
 
 	* 34344: Test/V07pcre.ztst: fix 34338, builtins need loading too
-	
+
 	* 34338: Test/V07pcre.ztst: check feature availability
 
 2015-01-22  Marc Finet  <m.dreadlock@gmail.com>
@@ -8348,7 +8356,7 @@
 2014-12-17  Barton E. Schaefer  <schaefer@zsh.org>
 
 	* 34002: Src/Zle/zle_keymap.c: zshcalloc() in init_keymaps()
-	
+
 	* 33992: Src/jobs.c: do not attempt attachtty() for process group
 	zero (which is possible in a linux pid namespace)
 
diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo
index c675cba3e..f3cec345d 100644
--- a/Doc/Zsh/params.yo
+++ b/Doc/Zsh/params.yo
@@ -109,10 +109,19 @@ of subscript expression that may be used when directly subscripting a
 variable name, described in the section Array Subscripts below, are not
 available.
 
-When assigning with this third form, every element must use this syntax or
-an error is generated.  Likewise, if the first entry does not match this
-form, any later entry that does is taken  as a simple value rather than a
-key / value pair.  Both var(key) and var(value) undergo all forms of expansion
+The syntaxes with and without the explicit key may be mixed.  Any absent
+var(key) is deduced by incrementing the index from the previously
+assigned element.  Note that it is not treated as an error
+if latter assignements in this form overwrite earlier assignments.
+
+For example, assuming the option tt(KSH_ARRAYS) is not set, the following:
+
+example(array=LPAR()one [3]=three four+RPAR())
+
+causes the array variable tt(array) to contain four elements tt(one),
+an empty string, tt(three) and tt(four), in that order.
+
+Both var(key) and var(value) undergo all forms of expansion
 allowed for single word shell expansions (this does not include filename
 generation); these are as performed by the parameter expansion flag
 tt(LPAR()e+RPAR()) as described in
@@ -120,7 +129,7 @@ ifzman(zmanref(zshparam))\
 ifnzman(noderef(Parameter Expansion)).
 Nested parentheses may surround var(value) and are included as part of the
 value, which is joined into a plain string; this differs from ksh which
-allows the values to themselves be arrays.  A future version of zsh may
+allows the values themselves to be arrays.  A future version of zsh may
 support that.  To cause the brackets to be interpreted as a character
 class for filename generation, and therefore to treat the resulting list
 of files as a set of values, quote the equal sign using any form of quoting.
@@ -159,6 +168,10 @@ indent(tt(set -A) var(name) var(key) var(value) ...)
 indent(var(name)tt(=LPAR())var(key) var(value) ...tt(RPAR()))
 indent(var(name)tt(=LPAR())tt([)var(key)tt(]=)var(value) ...tt(RPAR()))
 
+Note that only one of the two syntaxes above may be used in any
+given assignment; the forms may not be mixed.  This is unlike the case
+of numerically indexed arrays.
+
 Every var(key) must have a var(value) in this case.  Note that this
 assigns to the entire array, deleting any elements that do not appear in
 the list.  The append syntax may also be used with an associative array:
diff --git a/NEWS b/NEWS
index 796a2c90d..77f13bb3e 100644
--- a/NEWS
+++ b/NEWS
@@ -4,7 +4,7 @@ CHANGES FROM PREVIOUS VERSIONS OF ZSH
 
 Note also the list of incompatibilities in the README file.
 
-Changes from 5.4 to 5.4.3
+Changes from 5.4.2 to 5.5
 -------------------------
 
 The effect of the NO_INTERACTIVE_COMMENTS option extends into $(...) and
@@ -12,8 +12,17 @@ The effect of the NO_INTERACTIVE_COMMENTS option extends into $(...) and
 comments were always recognized within command substitutions unless the
 comment character "#" was disabled via reset of $histchars.
 
-Changes from 5.3.1 to 5.4
--------------------------
+An alternative assignment syntax for indicating indices for arrays
+and keys for associative arrays:
+
+typeset -a array=([1]=first [2]=second)
+typeset -A assoc=([key1]=val1 [key2]=val2)
+
+is allowed for compatibility with other shells.  In the case of normal
+arrays the new syntax can be mixed with the old.
+
+Changes from 5.3.1 to 5.4.2
+---------------------------
 
 The 'exec' and 'command' precommand modifiers, and options to them, are
 now parsed after parameter expansion.  Previously, both the modifier and
diff --git a/README b/README
index e22cfc12e..73733069d 100644
--- a/README
+++ b/README
@@ -252,6 +252,17 @@ This is also necessary in the unusual eventuality that the builtins are
 to be overridden by shell functions, since reserved words take
 precedence over functions.
 
+10) For compatilibity with other shells, the syntax
+
+array=([index]=value)
+
+can be used with both assoiative arrays and normal arrays.  In the
+unlikely event that you wish to create an array with an entry
+matching a file whose name consists of one of a range of characters
+matched as a [...] expression, followed by an equal sign, followed
+by arbitrary other charaters, it is now necessary to quote the equals
+sign.
+
 Incompatibilites between 5.0.7 and 5.0.8
 ----------------------------------------
 
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
index 5a9cccb6f..caeef7692 100644
--- a/Src/Zle/zle_tricky.c
+++ b/Src/Zle/zle_tricky.c
@@ -2268,7 +2268,7 @@ doexpansion(char *s, int lst, int olst, int explincmd)
 	int ng = opts[NULLGLOB];
 
 	opts[NULLGLOB] = 1;
-	globlist(vl, 1);
+	globlist(vl, PREFORK_NO_UNTOK);
 	opts[NULLGLOB] = ng;
     }
     if (errflag)
diff --git a/Src/exec.c b/Src/exec.c
index 31edfab55..bd242d140 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -2389,60 +2389,6 @@ addfd(int forked, int *save, struct multio **mfds, int fd1, int fd2, int rflag,
     }
 }
 
-/* Check for array assignent with entries like [key]=val.
- *
- * All entries or none must match this form, else error and return 0.
- *
- * Convert list to alternate key / val form, perform
- * appropriate substitution, and return 1 if found.
- *
- * Caller to check errflag.
- */
-
-/**/
-static int
-keyvalpairarray(LinkList vl, int htok)
-{
-    char *start, *end, *dat;
-    LinkNode ve, next;
-
-    if (vl &&
-	(ve = firstnode(vl)) &&
-	(start = (char *)getdata(ve)) &&
-	start[0] == Inbrack &&
-	(end = strchr(start+1, Outbrack)) &&
-	end[1] == Equals) {
-	for (;;) {
-	    *end = '\0';
-	    next = nextnode(ve);
-
-	    dat = start + 1;
-	    if (htok)
-		singsub(&dat);
-	    untokenize(dat);
-	    setdata(ve, dat);
-	    dat = end + 2;
-	    if (htok)
-		singsub(&dat);
-	    untokenize(dat);
-	    insertlinknode(vl, ve, dat);
-	    ve = next;
-	    if (!ve)
-		break;
-	    if (!(start = (char *)getdata(ve)) ||
-		start[0] != Inbrack ||
-		!(end = strchr(start+1, Outbrack)) ||
-		end[1] != Equals) {
-		zerr("bad array element, expected [key]=value: %s",
-		     start);
-		return 0;
-	    }
-	}
-	return 1;
-    }
-    return 0;
-}
-
 /**/
 static void
 addvars(Estate state, Wordcode pc, int addflags)
@@ -2484,10 +2430,6 @@ addvars(Estate state, Wordcode pc, int addflags)
 	    vl = &svl;
 	} else {
 	    vl = ecgetlist(state, WC_ASSIGN_NUM(ac), EC_DUPTOK, &htok);
-	    if (keyvalpairarray(vl, htok)) {
-		myflags |= ASSPM_KEY_VALUE;
-		htok = 0;
-	    }
 	    if (errflag) {
 		state->pc = opc;
 		return;
@@ -2495,25 +2437,28 @@ addvars(Estate state, Wordcode pc, int addflags)
 	}
 
 	if (vl && htok) {
+	    int prefork_ret = 0;
 	    prefork(vl, (isstr ? (PREFORK_SINGLE|PREFORK_ASSIGN) :
-			 PREFORK_ASSIGN), NULL);
+			 PREFORK_ASSIGN), &prefork_ret);
 	    if (errflag) {
 		state->pc = opc;
 		return;
 	    }
+	    if (prefork_ret & PREFORK_KEY_VALUE)
+		myflags |= ASSPM_KEY_VALUE;
 	    if (!isstr || (isset(GLOBASSIGN) && isstr &&
 			   haswilds((char *)getdata(firstnode(vl))))) {
-		globlist(vl, 0);
+		globlist(vl, prefork_ret);
 		/* Unset the parameter to force it to be recreated
 		 * as either scalar or array depending on how many
 		 * matches were found for the glob.
 		 */
 		if (isset(GLOBASSIGN) && isstr)
-		    unsetparam(name);
-	    }
-	    if (errflag) {
-		state->pc = opc;
-		return;
+			unsetparam(name);
+		if (errflag) {
+		    state->pc = opc;
+		    return;
+		}
 	    }
 	}
 	if (isstr && (empty(vl) || !nextnode(firstnode(vl)))) {
@@ -4030,16 +3975,17 @@ execcmd_exec(Estate state, Execcmd_params eparams,
 					  EC_DUPTOK, &htok);
 			    if (asg->value.array)
 			    {
-				if (keyvalpairarray(asg->value.array, 1))
-				    asg->flags |= ASG_KEY_VALUE;
-				else if (!errflag) {
+				if (!errflag) {
+				    int prefork_ret = 0;
 				    prefork(asg->value.array, PREFORK_ASSIGN,
-					    NULL);
+					    &prefork_ret);
 				    if (errflag) {
 					state->pc = opc;
 					break;
 				    }
-				    globlist(asg->value.array, 0);
+				    if (prefork_ret & PREFORK_KEY_VALUE)
+					asg->flags |= ASG_KEY_VALUE;
+				    globlist(asg->value.array, prefork_ret);
 				}
 				if (errflag) {
 				    state->pc = opc;
diff --git a/Src/params.c b/Src/params.c
index d628ddf37..c7514de8a 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -2704,7 +2704,7 @@ setarrvalue(Value v, char **val)
 	    v->pm->gsu.a->setfn(v->pm, val);
     } else if (v->start == -1 && v->end == 0 &&
     	    PM_TYPE(v->pm->node.flags) == PM_HASHED) {
-    	arrhashsetfn(v->pm, val, 1);
+    	arrhashsetfn(v->pm, val, ASSPM_AUGMENT);
     } else if ((PM_TYPE(v->pm->node.flags) == PM_HASHED)) {
 	freearray(val);
 	zerr("%s: attempt to set slice of associative array",
@@ -3186,69 +3186,124 @@ assignaparam(char *s, char **val, int flags)
     if (flags & ASSPM_WARN)
 	check_warn_pm(v->pm, "array", created, may_warn_about_nested_vars);
 
-    if ((flags & ASSPM_KEY_VALUE) && (PM_TYPE(v->pm->node.flags) & PM_ARRAY)) {
-	/*
-	 * This is an ordinary array with key / value pairs.
-	 */
-	int maxlen, origlen;
-	char **aptr, **fullval;
-	zlong *subscripts = (zlong *)zhalloc(arrlen(val) * sizeof(zlong));
-	zlong *iptr = subscripts;
-	if (flags & ASSPM_AUGMENT) {
-	    maxlen = origlen = arrlen(v->pm->gsu.a->getfn(v->pm));
-	} else {
-	    maxlen = origlen = 0;
-	}
-	for (aptr = val; *aptr && aptr[1]; aptr += 2) {
-	    *iptr = mathevali(*aptr);
-	    if (*iptr < 0 ||
-		(!isset(KSHARRAYS) && *iptr == 0)) {
-		unqueue_signals();
-		zerr("bad subscript for direct array assignment: %s", *aptr);
+    /*
+     * At this point, we may have array entries consisting of
+     * - a Marker element --- normally allocated array entry but
+     *   with just Marker char and null
+     * - an array index element --- as normal for associative array,
+     *   but non-standard for normal array which we handle now.
+     * - a value for the indexed element.
+     * This only applies if the flag ASSPM_KEY_VALUE is passed in,
+     * indicating prefork() detected this syntax.
+     *
+     * For associative arrays we just junk the Makrer elements.
+     */
+    if (flags & ASSPM_KEY_VALUE) {
+	char **aptr;
+	if (PM_TYPE(v->pm->node.flags) & PM_ARRAY) {
+	    /*
+	     * This is an ordinary array with key / value pairs.
+	     */
+	    int maxlen, origlen, nextind;
+	    char **fullval;
+	    zlong *subscripts = (zlong *)zhalloc(arrlen(val) * sizeof(zlong));
+	    zlong *iptr = subscripts;
+	    if (flags & ASSPM_AUGMENT) {
+		maxlen = origlen = arrlen(v->pm->gsu.a->getfn(v->pm));
+	    } else {
+		maxlen = origlen = 0;
+	    }
+	    nextind = 0;
+	    for (aptr = val; *aptr; ) {
+		if (**aptr == Marker) {
+		    *iptr = mathevali(*++aptr);
+		    if (*iptr < 0 ||
+			(!isset(KSHARRAYS) && *iptr == 0)) {
+			unqueue_signals();
+			zerr("bad subscript for direct array assignment: %s", *aptr);
+			return NULL;
+		    }
+		    if (!isset(KSHARRAYS))
+			--*iptr;
+		    nextind = *iptr + 1;
+		    ++iptr;
+		    aptr += 2;
+		} else {
+		    ++nextind;
+		    ++aptr;
+		}
+		if (nextind > maxlen)
+		    maxlen = nextind;
+	    }
+	    fullval = zshcalloc((maxlen+1) * sizeof(char *));
+	    if (!fullval) {
+		zerr("array too large");
 		return NULL;
 	    }
-	    if (!isset(KSHARRAYS))
-		--*iptr;
-	    if (*iptr + 1 > maxlen)
-		maxlen = *iptr + 1;
-	    ++iptr;
-	}
-	fullval = zshcalloc((maxlen+1) * sizeof(char *));
-	if (!fullval) {
-	    zerr("array too large");
-	    return NULL;
-	}
-	fullval[maxlen] = NULL;
-	if (flags & ASSPM_AUGMENT) {
-	    char **srcptr = v->pm->gsu.a->getfn(v->pm);
-	    for (aptr = fullval; aptr <= fullval + origlen; aptr++) {
-		*aptr = ztrdup(*srcptr); 
-		srcptr++;
+	    fullval[maxlen] = NULL;
+	    if (flags & ASSPM_AUGMENT) {
+		char **srcptr = v->pm->gsu.a->getfn(v->pm);
+		for (aptr = fullval; aptr <= fullval + origlen; aptr++) {
+		    *aptr = ztrdup(*srcptr); 
+		    srcptr++;
+		}
 	    }
-	}
-	iptr = subscripts;
-	for (aptr = val; *aptr && aptr[1]; aptr += 2) {
-	    zsfree(*aptr);
-	    fullval[*iptr] = aptr[1];
-	    ++iptr;
-	}
-	if (*aptr) {		/* Shouldn't be possible */
-	    DPUTS(1, "Extra element in key / value array");
-	    zsfree(*aptr);
-	}
-	free(val);
-	for (aptr = fullval; aptr < fullval + maxlen; aptr++) {
+	    iptr = subscripts;
+	    nextind = 0;
+	    for (aptr = val; *aptr; ++aptr) {
+		if (**aptr == Marker) {
+		    zsfree(*aptr);
+		    zsfree(*++aptr); /* Index, no longer needed */
+		    fullval[*iptr] = *++aptr;
+		    nextind = *iptr + 1;
+		    ++iptr;
+		} else {
+		    fullval[nextind] = *aptr;
+		    ++nextind;
+		}
+		/* aptr now on value in both cases */
+	    }
+	    if (*aptr) {		/* Shouldn't be possible */
+		DPUTS(1, "Extra element in key / value array");
+		zsfree(*aptr);
+	    }
+	    free(val);
+	    for (aptr = fullval; aptr < fullval + maxlen; aptr++) {
+		/*
+		 * Remember we don't have sparse arrays but and they're null
+		 * terminated --- so any value we don't set has to be an
+		 * empty string.
+		 */
+		if (!*aptr)
+		    *aptr = ztrdup("");
+	    }
+	    setarrvalue(v, fullval);
+	    unqueue_signals();
+	    return v->pm;
+	} else if (PM_TYPE(v->pm->node.flags & PM_HASHED)) {
 	    /*
-	     * Remember we don't have sparse arrays but and they're null
-	     * terminated --- so any value we don't set has to be an
-	     * empty string.
+	     * We strictly enforce [key]=value syntax for associative
+	     * arrays.  Marker can only indicate a Marker / key / value
+	     * triad; it cannot be there by accident.
+	     *
+	     * It's too inefficient to strip Markers here, and they
+	     * can't be there in the other form --- so just ignore
+	     * them willy nilly lower down.
 	     */
-	    if (!*aptr)
-		*aptr = ztrdup("");
+	    for (aptr = val; *aptr; aptr += 3) {
+		if (**aptr != Marker) {
+		    unqueue_signals();
+		    freearray(val);
+		    zerr("bad [key]=value syntax for associative array");
+		    return NULL;
+		}
+	    }
+	} else {
+	    unqueue_signals();
+	    freearray(val);
+	    zerr("invalid use of [key]=value assignment syntax");
+	    return NULL;
 	}
-	setarrvalue(v, fullval);
-	unqueue_signals();
-	return v->pm;
     }
 
     if (flags & ASSPM_AUGMENT) {
@@ -3741,30 +3796,37 @@ nullsethashfn(UNUSED(Param pm), HashTable x)
 /* Function to set value of an association parameter using key/value pairs */
 
 /**/
-mod_export void
-arrhashsetfn(Param pm, char **val, int augment)
+static void
+arrhashsetfn(Param pm, char **val, int flags)
 {
     /* Best not to shortcut this by using the existing hash table,   *
      * since that could cause trouble for special hashes.  This way, *
      * it's up to pm->gsu.h->setfn() what to do.                     */
-    int alen = arrlen(val);
+    int alen = 0;
     HashTable opmtab = paramtab, ht = 0;
-    char **aptr = val;
+    char **aptr;
     Value v = (Value) hcalloc(sizeof *v);
     v->end = -1;
 
+    for (aptr = val; *aptr; ++aptr) {
+	if (**aptr != Marker)
+	    ++alen;
+    }
+
     if (alen % 2) {
 	freearray(val);
 	zerr("bad set of key/value pairs for associative array");
 	return;
     }
-    if (augment) {
+    if (flags & ASSPM_AUGMENT) {
 	ht = paramtab = pm->gsu.h->getfn(pm);
     }
-    if (alen && (!augment || !paramtab)) {
+    if (alen && (!(flags & ASSPM_AUGMENT) || !paramtab)) {
 	ht = paramtab = newparamtable(17, pm->node.nam);
     }
-    while (*aptr) {
+    for (aptr = val; *aptr; ) {
+	if (**aptr == Marker)
+	    zsfree(*aptr++);
 	/* The parameter name is ztrdup'd... */
 	v->pm = createparam(*aptr, PM_SCALAR|PM_UNSET);
 	/*
diff --git a/Src/subst.c b/Src/subst.c
index 5df2a8b2d..357dc9168 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -35,6 +35,41 @@
 /**/
 char nulstring[] = {Nularg, '\0'};
 
+/* Check for array assignent with entries like [key]=val.
+ *
+ * Insert Marker node, convert following nodes to list to alternate key
+ * / val form, perform appropriate substitution, and return last
+ * inserted (value) node if found.
+ *
+ * Caller to check errflag.
+ */
+
+/**/
+static LinkNode
+keyvalpairelement(LinkList list, LinkNode node)
+{
+    char *start, *end, *dat;
+
+    if ((start = (char *)getdata(node)) &&
+	start[0] == Inbrack &&
+	(end = strchr(start+1, Outbrack)) &&
+	end[1] == Equals) {
+	static char marker[2] = { Marker, '\0' };
+	*end = '\0';
+
+	dat = start + 1;
+	singsub(&dat);
+	untokenize(dat);
+	setdata(node, marker);
+	node = insertlinknode(list, node, dat);
+	dat = end + 2;
+	singsub(&dat);
+	untokenize(dat);
+	return insertlinknode(list, node, dat);
+    }
+    return NULL;
+}
+
 /* Do substitutions before fork. These are:
  *  - Process substitution: <(...), >(...), =(...)
  *  - Parameter substitution
@@ -46,17 +81,16 @@ char nulstring[] = {Nularg, '\0'};
  *
  * "flag"s contains PREFORK_* flags, defined in zsh.h.
  *
- * "ret_flags" is used to return values from nested parameter
- * substitions.  It may be NULL in which case PREFORK_SUBEXP
- * must not appear in flags; any return value from below
- * will be discarded.
+ * "ret_flags" is used to return PREFORK_* values from nested parameter
+ * substitions.  It may be NULL in which case PREFORK_SUBEXP must not
+ * appear in flags; any return value from below will be discarded.
  */
 
 /**/
 mod_export void
 prefork(LinkList list, int flags, int *ret_flags)
 {
-    LinkNode node, stop = 0;
+    LinkNode node, insnode, stop = 0;
     int keep = 0, asssub = (flags & PREFORK_TYPESET) && isset(KSHTYPESET);
     int ret_flags_local = 0;
     if (!ret_flags)
@@ -64,6 +98,14 @@ prefork(LinkList list, int flags, int *ret_flags)
 
     queue_signals();
     for (node = firstnode(list); node; incnode(node)) {
+	if ((flags & (PREFORK_SINGLE|PREFORK_ASSIGN)) == PREFORK_ASSIGN &&
+	    (insnode = keyvalpairelement(list, node))) {
+	    node = insnode;
+	    *ret_flags |= PREFORK_KEY_VALUE;
+	    continue;
+	}
+	if (errflag)
+	    return;
 	if (isset(SHFILEEXPANSION)) {
 	    /*
 	     * Here and below we avoid taking the address
@@ -400,16 +442,31 @@ quotesubst(char *str)
     return str;
 }
 
+/* Glob entries of a linked list.
+ *
+ * flags are from PREFORK_*, but only two are handled:
+ * - PREFORK_NO_UNTOK: pass into zglob() a flag saying do not untokenise.
+ * - PREFORK_KEY_VALUE: look out for Marker / Key / Value list triads
+ *   and don't glob them.  The key and value should already have
+ *   been untokenised as they are not subject to further expansion.
+ */
+
 /**/
 mod_export void
-globlist(LinkList list, int nountok)
+globlist(LinkList list, int flags)
 {
     LinkNode node, next;
 
     badcshglob = 0;
     for (node = firstnode(list); !errflag && node; node = next) {
 	next = nextnode(node);
-	zglob(list, node, nountok);
+	if ((flags & PREFORK_KEY_VALUE) &&
+	    *(char *)getdata(node) == Marker) {
+	    /* Skip key / value pair */
+	    next = nextnode(nextnode(next));
+	} else {
+	    zglob(list, node, (flags & PREFORK_NO_UNTOK) != 0);
+	}
     }
     if (noerrs)
 	badcshglob = 0;
diff --git a/Src/zsh.h b/Src/zsh.h
index 27642f239..f7242dd34 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -223,9 +223,14 @@ struct mathfunc {
  * tokens here.
  */
 /*
- * Marker used in paramsubst for rc_expand_param.
- * Also used in pattern character arrays as guaranteed not to
- * mark a character in a string.
+ * Marker is used in the following special circumstances:
+ * - In paramsubst for rc_expand_param.
+ * - In pattern character arrays as guaranteed not to mark a character in
+ *   a string.
+ * - In assignments with the ASSPM_KEY_VALUE flag set in order to
+ *   mark that there is a key / value pair following.
+ * All the above are local uses --- any case where the Marker has
+ * escaped beyond the context in question is an error.
  */
 #define Marker		((char) 0xa2)
 
@@ -1969,7 +1974,14 @@ enum {
     /* SHWORDSPLIT forced off in nested subst */
     PREFORK_NOSHWORDSPLIT = 0x20,
     /* Prefork is part of a parameter subexpression */
-    PREFORK_SUBEXP        = 0x40
+    PREFORK_SUBEXP        = 0x40,
+    /* Prefork detected an assignment list with [key]=value syntax,
+     * Only used on return from prefork, not meaningful passed down.
+     * Also used as flag to globlist.
+     */
+    PREFORK_KEY_VALUE     = 0x80,
+    /* No untokenise: used only as flag to globlist */
+    PREFORK_NO_UNTOK      = 0x100
 };
 
 /*
diff --git a/Test/B02typeset.ztst b/Test/B02typeset.ztst
index 7923ae3a6..13f0d5e30 100644
--- a/Test/B02typeset.ztst
+++ b/Test/B02typeset.ztst
@@ -778,3 +778,18 @@
 >the key
 >the value
 >typeset -A keyvalhash=( ['*']='?not_globbed?' ['another key']='another value' ['the key']='the value' )
+
+  local keyvalarray=(first [2]=second third [6]=sixth seventh [5]=fifth new_sixth)
+  print -l "${keyvalarray[@]}"
+0:mixed syntax [key]=val with normal arrays
+>first
+>second
+>third
+>
+>fifth
+>new_sixth
+>seventh
+
+  local -A keyvalhash=(1 one [2]=two 3 three)
+1:Mixed syntax with [key]=val not allowed for hash.
+?(eval):1: bad [key]=value syntax for associative array
diff --git a/Test/D04parameter.ztst b/Test/D04parameter.ztst
index 367bca120..abac8f7a0 100644
--- a/Test/D04parameter.ztst
+++ b/Test/D04parameter.ztst
@@ -2275,3 +2275,30 @@ F:behavior, see http://austingroupbugs.net/view.php?id=888
 >third value
 >4fourth element\
 >fourth value
+
+  local keyvalarray
+  keyvalarray=(first [2]=second third [6]=sixth seventh [5]=fifth new_sixth)
+  print -l "${keyvalarray[@]}"
+0:mixed syntax [key]=val with normal arrays
+>first
+>second
+>third
+>
+>fifth
+>new_sixth
+>seventh
+
+  local -A keyvalhash
+  keyvalhash=(1 one [2]=two 3 three)
+1:Mixed syntax with [key]=val not allowed for hash.
+?(eval):2: bad [key]=value syntax for associative array
+
+  touch KVA1one KVA2two KVA3three
+  local keyvalarray
+  keyvalarray=(KVA* [4]=*)
+  print -l "${keyvalarray[@]}"
+0:Globbing in non-[key]=val parts of mixed syntax.
+>KVA1one
+>KVA2two
+>KVA3three
+>*