changeset 17646:e5397617d6ca v8.1.1820

patch 8.1.1820: using expr->FuncRef() does not work commit https://github.com/vim/vim/commit/761fdf01c6e307c448cec2684f8b315ba6d1f454 Author: Bram Moolenaar <Bram@vim.org> Date: Mon Aug 5 23:10:16 2019 +0200 patch 8.1.1820: using expr->FuncRef() does not work Problem: Using expr->FuncRef() does not work. Solution: Make FuncRef work as a method.
author Bram Moolenaar <Bram@vim.org>
date Mon, 05 Aug 2019 23:15:05 +0200
parents db112d38c152
children a944c60c3ef6
files src/eval.c src/testdir/test_method.vim src/userfunc.c src/version.c
diffstat 4 files changed, 117 insertions(+), 92 deletions(-) [+]
line wrap: on
line diff
--- a/src/eval.c
+++ b/src/eval.c
@@ -3655,6 +3655,74 @@ pattern_match(char_u *pat, char_u *text,
 }
 
 /*
+ * Handle a name followed by "(".  Both for just "name(arg)" and for
+ * "expr->name(arg)".
+ * Returns OK or FAIL.
+ */
+    static int
+eval_func(
+	char_u	    **arg,	// points to "(", will be advanced
+	char_u	    *name,
+	int	    name_len,
+	typval_T    *rettv,
+	int	    evaluate,
+	typval_T    *basetv)	// "expr" for "expr->name(arg)"
+{
+    char_u	*s = name;
+    int		len = name_len;
+    partial_T	*partial;
+    int		ret = OK;
+
+    if (!evaluate)
+	check_vars(s, len);
+
+    /* If "s" is the name of a variable of type VAR_FUNC
+     * use its contents. */
+    s = deref_func_name(s, &len, &partial, !evaluate);
+
+    /* Need to make a copy, in case evaluating the arguments makes
+     * the name invalid. */
+    s = vim_strsave(s);
+    if (s == NULL)
+	ret = FAIL;
+    else
+    {
+	funcexe_T funcexe;
+
+	// Invoke the function.
+	vim_memset(&funcexe, 0, sizeof(funcexe));
+	funcexe.firstline = curwin->w_cursor.lnum;
+	funcexe.lastline = curwin->w_cursor.lnum;
+	funcexe.doesrange = &len;
+	funcexe.evaluate = evaluate;
+	funcexe.partial = partial;
+	funcexe.basetv = basetv;
+	ret = get_func_tv(s, len, rettv, arg, &funcexe);
+    }
+    vim_free(s);
+
+    /* If evaluate is FALSE rettv->v_type was not set in
+     * get_func_tv, but it's needed in handle_subscript() to parse
+     * what follows. So set it here. */
+    if (rettv->v_type == VAR_UNKNOWN && !evaluate && **arg == '(')
+    {
+	rettv->vval.v_string = NULL;
+	rettv->v_type = VAR_FUNC;
+    }
+
+    /* Stop the expression evaluation when immediately
+     * aborting on error, or when an interrupt occurred or
+     * an exception was thrown but not caught. */
+    if (evaluate && aborting())
+    {
+	if (ret == OK)
+	    clear_tv(rettv);
+	ret = FAIL;
+    }
+    return ret;
+}
+
+/*
  * The "evaluate" argument: When FALSE, the argument is only parsed but not
  * executed.  The function may return OK, but the rettv will be of type
  * VAR_UNKNOWN.  The function still returns FAIL for a syntax error.
@@ -4671,55 +4739,7 @@ eval7(
 	else
 	{
 	    if (**arg == '(')		/* recursive! */
-	    {
-		partial_T *partial;
-
-		if (!evaluate)
-		    check_vars(s, len);
-
-		/* If "s" is the name of a variable of type VAR_FUNC
-		 * use its contents. */
-		s = deref_func_name(s, &len, &partial, !evaluate);
-
-		/* Need to make a copy, in case evaluating the arguments makes
-		 * the name invalid. */
-		s = vim_strsave(s);
-		if (s == NULL)
-		    ret = FAIL;
-		else
-		{
-		    funcexe_T funcexe;
-
-		    // Invoke the function.
-		    vim_memset(&funcexe, 0, sizeof(funcexe));
-		    funcexe.firstline = curwin->w_cursor.lnum;
-		    funcexe.lastline = curwin->w_cursor.lnum;
-		    funcexe.doesrange = &len;
-		    funcexe.evaluate = evaluate;
-		    funcexe.partial = partial;
-		    ret = get_func_tv(s, len, rettv, arg, &funcexe);
-		}
-		vim_free(s);
-
-		/* If evaluate is FALSE rettv->v_type was not set in
-		 * get_func_tv, but it's needed in handle_subscript() to parse
-		 * what follows. So set it here. */
-		if (rettv->v_type == VAR_UNKNOWN && !evaluate && **arg == '(')
-		{
-		    rettv->vval.v_string = NULL;
-		    rettv->v_type = VAR_FUNC;
-		}
-
-		/* Stop the expression evaluation when immediately
-		 * aborting on error, or when an interrupt occurred or
-		 * an exception was thrown but not caught. */
-		if (evaluate && aborting())
-		{
-		    if (ret == OK)
-			clear_tv(rettv);
-		    ret = FAIL;
-		}
-	    }
+		ret = eval_func(arg, s, len, rettv, evaluate, NULL);
 	    else if (evaluate)
 		ret = get_var_tv(s, len, rettv, NULL, TRUE, FALSE);
 	    else
@@ -4815,55 +4835,42 @@ eval_method(
 {
     char_u	*name;
     long	len;
-    funcexe_T	funcexe;
-    int		ret = OK;
+    char_u	*alias;
     typval_T	base = *rettv;
+    int		ret;
 
     // Skip over the ->.
     *arg += 2;
-
-    // Locate the method name.
+    rettv->v_type = VAR_UNKNOWN;
+
     name = *arg;
-    for (len = 0; eval_isnamec(name[len]); ++len)
-	;
-    if (len == 0)
+    len = get_name_len(arg, &alias, evaluate, TRUE);
+    if (alias != NULL)
+	name = alias;
+
+    if (len <= 0)
     {
 	if (verbose)
 	    emsg(_("E260: Missing name after ->"));
-	return FAIL;
-    }
-
-    // Check for the "(".  Skip over white space after it.
-    if (name[len] != '(')
-    {
-	if (verbose)
-	    semsg(_(e_missingparen), name);
-	return FAIL;
-    }
-    *arg += len;
-
-    // TODO: if "name" is a function reference, resolve it.
-
-    vim_memset(&funcexe, 0, sizeof(funcexe));
-    funcexe.evaluate = evaluate;
-    funcexe.basetv = &base;
-    rettv->v_type = VAR_UNKNOWN;
-    ret = get_func_tv(name, len, rettv, arg, &funcexe);
+	ret = FAIL;
+    }
+    else
+    {
+	if (**arg != '(')
+	{
+	    if (verbose)
+		semsg(_(e_missingparen), name);
+	    ret = FAIL;
+	}
+	else
+	    ret = eval_func(arg, name, len, rettv, evaluate, &base);
+    }
 
     /* Clear the funcref afterwards, so that deleting it while
      * evaluating the arguments is possible (see test55). */
     if (evaluate)
 	clear_tv(&base);
 
-    /* Stop the expression evaluation when immediately aborting on
-     * error, or when an interrupt occurred or an exception was thrown
-     * but not caught. */
-    if (aborting())
-    {
-	if (ret == OK)
-	    clear_tv(rettv);
-	ret = FAIL;
-    }
     return ret;
 }
 
--- a/src/testdir/test_method.vim
+++ b/src/testdir/test_method.vim
@@ -1,6 +1,6 @@
 " Tests for ->method()
 
-func Test_list()
+func Test_list_method()
   let l = [1, 2, 3]
   call assert_equal([1, 2, 3, 4], [1, 2, 3]->add(4))
   eval l->assert_equal(l)
@@ -34,7 +34,7 @@ func Test_list()
   call assert_fails('eval l->values()', 'E715:')
 endfunc
 
-func Test_dict()
+func Test_dict_method()
   let d = #{one: 1, two: 2, three: 3}
 
   call assert_equal(d, d->copy())
@@ -66,7 +66,7 @@ func Test_dict()
   call assert_equal([1, 2, 3], d->values())
 endfunc
 
-func Test_string()
+func Test_string_method()
   call assert_equal(['1', '2', '3'], '1 2 3'->split())
   call assert_equal([1, 2, 3], '1 2 3'->split()->map({i, v -> str2nr(v)}))
   call assert_equal([65, 66, 67], 'ABC'->str2list())
@@ -76,7 +76,7 @@ func Test_string()
   call assert_equal('axc', 'abc'->substitute('b', 'x', ''))
 endfunc
 
-func Test_append()
+func Test_method_append()
   new
   eval ['one', 'two', 'three']->append(1)
   call assert_equal(['', 'one', 'two', 'three'], getline(1, '$'))
@@ -89,3 +89,16 @@ func Test_append()
 
   exe 'bwipe! ' .. bnr
 endfunc
+
+func Test_method_funcref()
+  func Concat(one, two, three)
+    return a:one .. a:two .. a:three
+  endfunc
+  let FuncRef = function('Concat')
+  eval 'foo'->FuncRef('bar', 'tail')->assert_equal('foobartail')
+
+  let Partial = function('Concat', ['two'])
+  eval 'one'->Partial('three')->assert_equal('onetwothree')
+
+  delfunc Concat
+endfunc
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -1498,6 +1498,7 @@ call_func(
     typval_T	argv[MAX_FUNC_ARGS + 1]; // used when "partial" or
 					 // "funcexe->basetv" is not NULL
     int		argv_clear = 0;
+    int		argv_base = 0;
     partial_T	*partial = funcexe->partial;
 
     // Make a copy of the name, if it comes from a funcref variable it could
@@ -1590,9 +1591,9 @@ call_func(
 		    mch_memmove(&argv[1], argvars, sizeof(typval_T) * argcount);
 		    argv[0] = *funcexe->basetv;
 		    argcount++;
+		    argvars = argv;
+		    argv_base = 1;
 		}
-		else
-		    memcpy(argv, argvars, sizeof(typval_T) * argcount);
 
 		if (fp->uf_flags & FC_RANGE && funcexe->doesrange != NULL)
 		    *funcexe->doesrange = TRUE;
@@ -1621,7 +1622,7 @@ call_func(
 			did_save_redo = TRUE;
 		    }
 		    ++fp->uf_calls;
-		    call_user_func(fp, argcount, argv, rettv,
+		    call_user_func(fp, argcount, argvars, rettv,
 					 funcexe->firstline, funcexe->lastline,
 				  (fp->uf_flags & FC_DICT) ? selfdict : NULL);
 		    if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0)
@@ -1698,8 +1699,10 @@ call_func(
 	}
     }
 
+    // clear the copies made from the partial
     while (argv_clear > 0)
-	clear_tv(&argv[--argv_clear]);
+	clear_tv(&argv[--argv_clear + argv_base]);
+
     vim_free(tofree);
     vim_free(name);
 
--- a/src/version.c
+++ b/src/version.c
@@ -774,6 +774,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1820,
+/**/
     1819,
 /**/
     1818,