Subject: Re: Aliasing assignment-ish words (aliases[x=y]=z)
From: Daniel Shahaf <[email-masked]>
To: [email-masked]
Date: Wed, 13 Jan 2016 00:58:14 +0000
List-Id: Zsh Workers List <zsh-workers.zsh.org>

Bart Schaefer wrote on Mon, Jan 11, 2016 at 23:44:29 -0800:
> On Jan 10,  7:50pm, Daniel Shahaf wrote:
> } For what it's worth, the real-world use-case behind this thread is
> } a z-sy-h user whose zshrc has an alias "aliases[=]='noglob ='" and
> } a function literally named '='.  (That alias works as expected, as
> } does "aliases[=ls]='...'" even if EQUALS is set.)
> 
> I find this interesting:
> 
>     torch% alias '==foo'
>     zsh: bad assignment
> 
> "Bad assignment"?

Comes from getasg() which is shared core of 'alias' 'hash' 'typeset'.
 
> Hm, it also ought to be possible to special case a word starting with
> '=' (since it's not possible to alias the empty string to anything).

The enclosed patch implements that.  However, I have no opinion as to
whether it should be committed.

To be applied on top of the patch I just sent.

diff --git a/Src/builtin.c b/Src/builtin.c
index b06bc6d..d8974eb 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -1855,11 +1855,13 @@ fcedit(char *ename, char *fn)
 /* Separate an argument into name=value parts, returning them in an     *
  * asgment structure.  Because the asgment structure used is global,    *
  * only one of these can be active at a time.  The string s gets placed *
- * in this global structure, so it needs to be in permanent memory.     */
+ * in this global structure, so it needs to be in permanent memory.     *
+ *                                                                      *
+ * If allow_equals is set, the 'name' part may start with a '='.        */
 
 /**/
 static Asgment
-getasg(char ***argvp, LinkList assigns)
+getasg(char ***argvp, LinkList assigns, int allow_equals)
 {
     char *s = **argvp;
     static struct asgment asg;
@@ -1877,7 +1879,7 @@ getasg(char ***argvp, LinkList assigns)
     }
 
     /* check if name is empty */
-    if (*s == '=') {
+    if (*s == '=' && !allow_equals) {
 	zerr("bad assignment");
 	return NULL;
     }
@@ -1885,6 +1887,8 @@ getasg(char ***argvp, LinkList assigns)
     asg.is_array = 0;
 
     /* search for `=' */
+    if (*s == '=' && allow_equals)
+	++s;
     for (; *s && *s != '='; s++);
 
     /* found `=', so return with a value */
@@ -2622,7 +2626,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
 	    return 1;
 	}
 
-	if (!(asg = getasg(&argv, assigns))) {
+	if (!(asg = getasg(&argv, assigns, 0))) {
 	    unqueue_signals();
 	    return 1;
 	}
@@ -2634,7 +2638,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
 	    return 1;
 	}
 
-	if (!(asg = getasg(&argv, assigns))) {
+	if (!(asg = getasg(&argv, assigns, 0))) {
 	    unqueue_signals();
 	    return 1;
 	}
@@ -2794,7 +2798,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
 		printflags |= PRINT_NAMEONLY;
 	}
 
-	while ((asg = getasg(&argv, assigns))) {
+	while ((asg = getasg(&argv, assigns, 0))) {
 	    LinkList pmlist = newlinklist();
 	    LinkNode pmnode;
 
@@ -2840,7 +2844,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
     }
 
     /* Take arguments literally.  Don't glob */
-    while ((asg = getasg(&argv, assigns))) {
+    while ((asg = getasg(&argv, assigns, 0))) {
 	HashNode hn = (paramtab == realparamtab ?
 		       /* getnode2() to avoid autoloading */
 		       paramtab->getnode2(paramtab, asg->name) :
@@ -3715,7 +3719,7 @@ bin_hash(char *name, char **argv, Options ops, UNUSED(int func))
 	    argv++;
             continue;
 	}
-        if (!(asg = getasg(&argv, NULL))) {
+        if (!(asg = getasg(&argv, NULL, 0))) {
 	    zwarnnam(name, "bad assignment");
 	    returnval = 1;
         } else if (ASG_VALUEP(asg)) {
@@ -3947,7 +3951,7 @@ bin_alias(char *name, char **argv, Options ops, UNUSED(int func))
 
     /* Take arguments literally.  Don't glob */
     queue_signals();
-    while ((asg = getasg(&argv, NULL))) {
+    while ((asg = getasg(&argv, NULL, 1))) {
 	if (asg->value.scalar && !OPT_ISSET(ops,'L')) {
 	    /* The argument is of the form foo=bar and we are not *
 	     * forcing a listing with -L, so define an alias      */
diff --git a/Src/hashtable.c b/Src/hashtable.c
index 0664c36..51155cf 100644
--- a/Src/hashtable.c
+++ b/Src/hashtable.c
@@ -1277,7 +1277,7 @@ printaliasnode(HashNode hn, int printflags)
 
     if (printflags & PRINT_LIST) {
 	/* Fast fail on unrepresentable values. */
-	if (strchr(a->node.nam, '=')) {
+	if (strchr(a->node.nam + 1, '=')) {
 	    zwarn("invalid alias '%s' encountered while printing aliases", 
 		  a->node.nam);
 	    /* ### TODO: Return an error status to the C caller */
diff --git a/Test/A02alias.ztst b/Test/A02alias.ztst
index cfa9dae..24dbbd7 100644
--- a/Test/A02alias.ztst
+++ b/Test/A02alias.ztst
@@ -104,3 +104,16 @@
 >0
 ?(eval):2: invalid alias 'x=y' encountered while printing aliases
 # Currently, 'alias -L' returns 0 in this case.  Perhaps it should return 1.
+
+  alias \=="echo hello world"
+  alias \=foo="echo hello foo"
+  eval "="
+  eval "=foo"
+  eval "=bar"
+  # Test the EQUALS option
+  eval "=pwd"
+0:alias whose LHS starts with '='
+>hello world
+>hello foo
+*>*Test*
+?(eval):1: bar not found