changeset 26721:9c9b8d95b05f v8.2.3889

patch 8.2.3889: duplicate code for translating script-local function name Commit: https://github.com/vim/vim/commit/e7f4abd38b6e05100c699900c8f87281e363beb2 Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Fri Dec 24 20:47:38 2021 +0000 patch 8.2.3889: duplicate code for translating script-local function name Problem: Duplicate code for translating script-local function name. Solution: Move the code to get_scriptlocal_funcname(). (Yegappan Lakshmanan, closes #9393)
author Bram Moolenaar <Bram@vim.org>
date Fri, 24 Dec 2021 22:00:03 +0100
parents 7d4b0da6a26b
children f4e86e7b1427
files src/evalfunc.c src/evalvars.c src/option.c src/proto/userfunc.pro src/testdir/test_expr.vim src/testdir/test_normal.vim src/userfunc.c src/version.c
diffstat 8 files changed, 84 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -4050,22 +4050,11 @@ common_function(typval_T *argvars, typva
 	list_T	*list = NULL;
 
 	if (STRNCMP(s, "s:", 2) == 0 || STRNCMP(s, "<SID>", 5) == 0)
-	{
-	    char	sid_buf[25];
-	    int		off = *s == 's' ? 2 : 5;
-
 	    // Expand s: and <SID> into <SNR>nr_, so that the function can
 	    // also be called from another script. Using trans_function_name()
 	    // would also work, but some plugins depend on the name being
 	    // printable text.
-	    sprintf(sid_buf, "<SNR>%ld_", (long)current_sctx.sc_sid);
-	    name = alloc(STRLEN(sid_buf) + STRLEN(s + off) + 1);
-	    if (name != NULL)
-	    {
-		STRCPY(name, sid_buf);
-		STRCAT(name, s + off);
-	    }
-	}
+	    name = get_scriptlocal_funcname(s);
 	else
 	    name = vim_strsave(s);
 
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -4450,7 +4450,18 @@ get_callback(typval_T *arg)
 	    r = FAIL;
 	else if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING)
 	{
-	    // Note that we don't make a copy of the string.
+	    if (arg->v_type == VAR_STRING)
+	    {
+		char_u *name;
+
+		name = get_scriptlocal_funcname(arg->vval.v_string);
+		if (name != NULL)
+		{
+		    vim_free(arg->vval.v_string);
+		    arg->vval.v_string = name;
+		}
+	    }
+
 	    res.cb_name = arg->vval.v_string;
 	    func_ref(res.cb_name);
 	}
--- a/src/option.c
+++ b/src/option.c
@@ -7205,33 +7205,8 @@ option_set_callback_func(char_u *optval 
 	// Lambda expression or a funcref
 	tv = eval_expr(optval, NULL);
     else
-    {
 	// treat everything else as a function name string
-
-	// Function name starting with "s:" are supported only in a vimscript
-	// context.
-	if (STRNCMP(optval, "s:", 2) == 0)
-	{
-	    char	sid_buf[25];
-	    char_u	*funcname;
-
-	    if (!SCRIPT_ID_VALID(current_sctx.sc_sid))
-	    {
-		emsg(_(e_using_sid_not_in_script_context));
-		return FAIL;
-	    }
-	    // Expand s: prefix into <SNR>nr_<name>
-	    sprintf(sid_buf, "<SNR>%ld_", (long)current_sctx.sc_sid);
-	    funcname = alloc(STRLEN(sid_buf) + STRLEN(optval + 2) + 1);
-	    if (funcname == NULL)
-		return FAIL;
-	    STRCPY(funcname, sid_buf);
-	    STRCAT(funcname, optval + 2);
-	    tv = alloc_string_tv(funcname);
-	}
-	else
-	    tv = alloc_string_tv(vim_strsave(optval));
-    }
+	tv = alloc_string_tv(vim_strsave(optval));
     if (tv == NULL)
 	return FAIL;
 
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -35,6 +35,7 @@ int call_func(char_u *funcname, int len,
 char_u *printable_func_name(ufunc_T *fp);
 char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial, type_T **type);
 char_u *untrans_function_name(char_u *name);
+char_u *get_scriptlocal_funcname(char_u *funcname);
 char_u *save_function_name(char_u **name, int *is_global, int skip, int flags, funcdict_T *fudi);
 void list_functions(regmatch_T *regmatch);
 ufunc_T *define_function(exarg_T *eap, char_u *name_arg);
--- a/src/testdir/test_expr.vim
+++ b/src/testdir/test_expr.vim
@@ -639,6 +639,21 @@ func Test_funcref()
   call assert_fails('echo test_null_function()->funcref()', 'E475: Invalid argument: NULL')
 endfunc
 
+" Test for calling function() and funcref() outside of a Vim script context.
+func Test_function_outside_script()
+  let cleanup =<< trim END
+    call writefile([execute('messages')], 'Xtest.out')
+    qall
+  END
+  call writefile(cleanup, 'Xverify.vim')
+  call RunVim([], [], "-c \"echo function('s:abc')\" -S Xverify.vim")
+  call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0])
+  call RunVim([], [], "-c \"echo funcref('s:abc')\" -S Xverify.vim")
+  call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0])
+  call delete('Xtest.out')
+  call delete('Xverify.vim')
+endfunc
+
 func Test_setmatches()
   let lines =<< trim END
       hi def link 1 Comment
--- a/src/testdir/test_normal.vim
+++ b/src/testdir/test_normal.vim
@@ -642,6 +642,18 @@ func Test_opfunc_callback()
   END
   call CheckScriptSuccess(lines)
 
+  " setting 'opfunc' to a script local function outside of a script context
+  " should fail
+  let cleanup =<< trim END
+    call writefile([execute('messages')], 'Xtest.out')
+    qall
+  END
+  call writefile(cleanup, 'Xverify.vim')
+  call RunVim([], [], "-c \"set opfunc=s:abc\" -S Xverify.vim")
+  call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0])
+  call delete('Xtest.out')
+  call delete('Xverify.vim')
+
   " cleanup
   set opfunc&
   delfunc OpFunc1
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -3876,6 +3876,46 @@ untrans_function_name(char_u *name)
 }
 
 /*
+ * If the 'funcname' starts with "s:" or "<SID>", then expands it to the
+ * current script ID and returns the expanded function name. The caller should
+ * free the returned name. If not called from a script context or the function
+ * name doesn't start with these prefixes, then returns NULL.
+ * This doesn't check whether the script-local function exists or not.
+ */
+    char_u *
+get_scriptlocal_funcname(char_u *funcname)
+{
+    char	sid_buf[25];
+    int		off;
+    char_u	*newname;
+
+    if (funcname == NULL)
+	return NULL;
+
+    if (STRNCMP(funcname, "s:", 2) != 0
+		&& STRNCMP(funcname, "<SID>", 5) != 0)
+	// The function name is not a script-local function name
+	return NULL;
+
+    if (!SCRIPT_ID_VALID(current_sctx.sc_sid))
+    {
+	emsg(_(e_using_sid_not_in_script_context));
+	return NULL;
+    }
+    // Expand s: prefix into <SNR>nr_<name>
+    vim_snprintf(sid_buf, sizeof(sid_buf), "<SNR>%ld_",
+	    (long)current_sctx.sc_sid);
+    off = *funcname == 's' ? 2 : 5;
+    newname = alloc(STRLEN(sid_buf) + STRLEN(funcname + off) + 1);
+    if (newname == NULL)
+	return NULL;
+    STRCPY(newname, sid_buf);
+    STRCAT(newname, funcname + off);
+
+    return newname;
+}
+
+/*
  * Call trans_function_name(), except that a lambda is returned as-is.
  * Returns the name in allocated memory.
  */
--- a/src/version.c
+++ b/src/version.c
@@ -750,6 +750,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3889,
+/**/
     3888,
 /**/
     3887,