Problem: Vim9: script function can be deleted.
Solution: Disallow deleting script function. Delete functions when sourcing
a script again.
int use_string = FALSE;
partial_T *arg_pt = NULL;
char_u *trans_name = NULL;
+ int is_global = FALSE;
if (argvars[0].v_type == VAR_FUNC)
{
if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref)
{
name = s;
- trans_name = trans_function_name(&name, FALSE,
+ trans_name = trans_function_name(&name, &is_global, FALSE,
TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL, NULL);
if (*name != NUL)
s = NULL;
- else if (trans_name != NULL
- && ASCII_ISUPPER(*s)
- && current_sctx.sc_version == SCRIPT_VERSION_VIM9
- && find_func(trans_name, NULL) == NULL)
- {
- // With Vim9 script "MyFunc" can be script-local to the current
- // script or global. The script-local name is not found, assume
- // global.
- vim_free(trans_name);
- trans_name = vim_strsave(s);
- }
}
if (s == NULL || *s == NUL || (use_string && VIM_ISDIGIT(*s))
semsg(_(e_invarg2), use_string ? tv_get_string(&argvars[0]) : s);
// Don't check an autoload name for existence here.
else if (trans_name != NULL && (is_funcref
- ? find_func(trans_name, NULL) == NULL
- : !translated_function_exists(trans_name)))
+ ? find_func(trans_name, is_global, NULL) == NULL
+ : !translated_function_exists(trans_name, is_global)))
semsg(_("E700: Unknown function: %s"), s);
else
{
}
else if (is_funcref)
{
- pt->pt_func = find_func(trans_name, NULL);
+ pt->pt_func = find_func(trans_name, is_global, NULL);
func_ptr_ref(pt->pt_func);
vim_free(name);
}
void emsg_funcname(char *ermsg, char_u *name);
int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, funcexe_T *funcexe);
char_u *fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error);
-ufunc_T *find_func(char_u *name, cctx_T *cctx);
+ufunc_T *find_func(char_u *name, int is_global, cctx_T *cctx);
int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv, funcexe_T *funcexe, dict_T *selfdict);
void save_funccal(funccal_entry_T *entry);
void restore_funccal(void);
funccall_T *get_current_funccal(void);
+void delete_script_functions(int sid);
void free_all_functions(void);
int builtin_function(char_u *name, int len);
int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv);
int call_callback(callback_T *callback, int len, typval_T *rettv, int argcount, typval_T *argvars);
void user_func_error(int error, char_u *name);
int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe);
-char_u *trans_function_name(char_u **pp, int skip, int flags, funcdict_T *fdp, partial_T **partial);
+char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial);
char_u *untrans_function_name(char_u *name);
void ex_function(exarg_T *eap);
int eval_fname_script(char_u *p);
-int translated_function_exists(char_u *name);
+int translated_function_exists(char_u *name, int is_global);
int has_varargs(ufunc_T *ufunc);
int function_exists(char_u *name, int no_deref);
char_u *get_expanded_name(char_u *name, int check);
hashitem_T *hi;
dictitem_T *di;
int todo;
+ int is_vim9 = si->sn_version == SCRIPT_VERSION_VIM9;
// loading the same script again
si->sn_had_command = FALSE;
// old imports are no longer valid
free_imports(sid);
+
+ // in Vim9 script functions are marked deleted
+ if (is_vim9)
+ delete_script_functions(sid);
}
else
{
assert_equal(true, g:adict == #{bbb: 8, aaa: 2})
assert_equal(false, #{ccc: 9, aaa: 2} == g:adict)
- assert_equal(true, function('Test_expr4_equal') == function('Test_expr4_equal'))
- assert_equal(false, function('Test_expr4_equal') == function('Test_expr4_is'))
+ assert_equal(true, function('g:Test_expr4_equal') == function('g:Test_expr4_equal'))
+ assert_equal(false, function('g:Test_expr4_equal') == function('g:Test_expr4_is'))
- assert_equal(true, function('Test_expr4_equal', [123]) == function('Test_expr4_equal', [123]))
- assert_equal(false, function('Test_expr4_equal', [123]) == function('Test_expr4_is', [123]))
- assert_equal(false, function('Test_expr4_equal', [123]) == function('Test_expr4_equal', [999]))
+ assert_equal(true, function('g:Test_expr4_equal', [123]) == function('g:Test_expr4_equal', [123]))
+ assert_equal(false, function('g:Test_expr4_equal', [123]) == function('g:Test_expr4_is', [123]))
+ assert_equal(false, function('g:Test_expr4_equal', [123]) == function('g:Test_expr4_equal', [999]))
enddef
" test != comperator
assert_equal(false, g:adict != #{bbb: 8, aaa: 2})
assert_equal(true, #{ccc: 9, aaa: 2} != g:adict)
- assert_equal(false, function('Test_expr4_equal') != function('Test_expr4_equal'))
- assert_equal(true, function('Test_expr4_equal') != function('Test_expr4_is'))
+ assert_equal(false, function('g:Test_expr4_equal') != function('g:Test_expr4_equal'))
+ assert_equal(true, function('g:Test_expr4_equal') != function('g:Test_expr4_is'))
- assert_equal(false, function('Test_expr4_equal', [123]) != function('Test_expr4_equal', [123]))
- assert_equal(true, function('Test_expr4_equal', [123]) != function('Test_expr4_is', [123]))
- assert_equal(true, function('Test_expr4_equal', [123]) != function('Test_expr4_equal', [999]))
+ assert_equal(false, function('g:Test_expr4_equal', [123]) != function('g:Test_expr4_equal', [123]))
+ assert_equal(true, function('g:Test_expr4_equal', [123]) != function('g:Test_expr4_is', [123]))
+ assert_equal(true, function('g:Test_expr4_equal', [123]) != function('g:Test_expr4_equal', [999]))
enddef
" test > comperator
def Test_expr7_trailing()
" user function call
- assert_equal(123, CallMe(123))
- assert_equal(123, CallMe( 123))
- assert_equal(123, CallMe(123 ))
- assert_equal('yesno', CallMe2('yes', 'no'))
- assert_equal('yesno', CallMe2( 'yes', 'no' ))
- assert_equal('nothing', CallMe('nothing'))
+ assert_equal(123, g:CallMe(123))
+ assert_equal(123, g:CallMe( 123))
+ assert_equal(123, g:CallMe(123 ))
+ assert_equal('yesno', g:CallMe2('yes', 'no'))
+ assert_equal('yesno', g:CallMe2( 'yes', 'no' ))
+ assert_equal('nothing', g:CallMe('nothing'))
" partial call
- let Part = function('CallMe')
+ let Part = function('g:CallMe')
assert_equal('yes', Part('yes'))
" funcref call, using list index
def Test_delfunc()
let lines =<< trim END
vim9script
- def GoneSoon()
+ def g:GoneSoon()
echo 'hello'
enddef
GoneSoon()
enddef
- delfunc GoneSoon
+ delfunc g:GoneSoon
CallGoneSoon()
END
writefile(lines, 'XToDelFunc')
endif
let Funky1: func
let Funky2: func = function('len')
- let Party2: func = funcref('Test_syntax')
+ let Party2: func = funcref('g:Test_syntax')
# type becomes list<any>
let somelist = rand() > 0 ? [1, 2, 3] : ['a', 'b', 'c']
assert_equal('', $ENVVAR)
enddef
+def Test_delfunction()
+ " Check function is defined in script namespace
+ CheckScriptSuccess([
+ 'vim9script',
+ 'func CheckMe()',
+ ' return 123',
+ 'endfunc',
+ 'assert_equal(123, s:CheckMe())',
+ ])
+
+ " Check function in script namespace cannot be deleted
+ CheckScriptFailure([
+ 'vim9script',
+ 'func DeleteMe1()',
+ 'endfunc',
+ 'delfunction DeleteMe1',
+ ], 'E1084:')
+ CheckScriptFailure([
+ 'vim9script',
+ 'func DeleteMe2()',
+ 'endfunc',
+ 'def DoThat()',
+ ' delfunction DeleteMe2',
+ 'enddef',
+ 'DoThat()',
+ ], 'E1084:')
+ CheckScriptFailure([
+ 'vim9script',
+ 'def DeleteMe3()',
+ 'enddef',
+ 'delfunction DeleteMe3',
+ ], 'E1084:')
+ CheckScriptFailure([
+ 'vim9script',
+ 'def DeleteMe4()',
+ 'enddef',
+ 'def DoThat()',
+ ' delfunction DeleteMe4',
+ 'enddef',
+ 'DoThat()',
+ ], 'E1084:')
+enddef
+
func Test_wrong_type()
call CheckDefFailure(['let var: list<nothing>'], 'E1010:')
call CheckDefFailure(['let var: list<list<nothing>>'], 'E1010:')
assert_fails('export something', 'E1043')
enddef
-def Test_vim9script_reload()
+def Test_vim9script_reload_import()
let lines =<< trim END
vim9script
const var = ''
delete('Ximport.vim')
enddef
+def Test_vim9script_reload_delfunc()
+ let first_lines =<< trim END
+ vim9script
+ def FuncYes(): string
+ return 'yes'
+ enddef
+ END
+ let middle_lines =<< trim END
+ def FuncNo(): string
+ return 'no'
+ enddef
+ END
+ let final_lines =<< trim END
+ def g:DoCheck(no_exists: bool)
+ assert_equal('yes', FuncYes())
+ if no_exists
+ assert_equal('no', FuncNo())
+ else
+ assert_fails('call FuncNo()', 'E117:')
+ endif
+ enddef
+ END
+
+ # FuncNo() is defined
+ writefile(first_lines + middle_lines + final_lines, 'Xreloaded.vim')
+ source Xreloaded.vim
+ g:DoCheck(true)
+
+ # FuncNo() is not redefined
+ writefile(first_lines + final_lines, 'Xreloaded.vim')
+ source Xreloaded.vim
+ g:DoCheck(false)
+
+ # FuncNo() is back
+ writefile(first_lines + middle_lines + final_lines, 'Xreloaded.vim')
+ source Xreloaded.vim
+ g:DoCheck(true)
+
+ delete('Xreloaded.vim')
+enddef
+
def Test_import_absolute()
let import_lines = [
'vim9script',
CheckScriptSuccess([
'vim9script',
- 'func DeleteMe()',
+ 'func g:DeleteMeA()',
'endfunc',
- 'delfunction DeleteMe # comment',
+ 'delfunction g:DeleteMeA # comment',
])
CheckScriptFailure([
'vim9script',
- 'func DeleteMe()',
+ 'func g:DeleteMeB()',
'endfunc',
- 'delfunction DeleteMe# comment',
+ 'delfunction g:DeleteMeB# comment',
], 'E488:')
CheckScriptSuccess([
{
ufunc_T *fp;
- fp = find_func(argvars[0].vval.v_string, NULL);
+ fp = find_func(argvars[0].vval.v_string, FALSE, NULL);
if (fp != NULL)
retval = fp->uf_refcount;
}
#define FC_DEAD 0x80 // function kept only for reference to dfunc
#define FC_EXPORT 0x100 // "export def Func()"
#define FC_NOARGS 0x200 // no a: variables in lambda
+#define FC_VIM9 0x400 // defined in vim9 script file
/*
* All user-defined functions are found in this hashtable.
/*
* Find a function by name, return pointer to it in ufuncs.
+ * When "is_global" is true don't find script-local or imported functions.
* Return NULL for unknown function.
*/
static ufunc_T *
-find_func_even_dead(char_u *name, cctx_T *cctx)
+find_func_even_dead(char_u *name, int is_global, cctx_T *cctx)
{
hashitem_T *hi;
ufunc_T *func;
imported_T *imported;
- if (in_vim9script())
+ if (in_vim9script() && !is_global)
{
// Find script-local function before global one.
func = find_func_with_sid(name, current_sctx.sc_sid);
* Return NULL for unknown or dead function.
*/
ufunc_T *
-find_func(char_u *name, cctx_T *cctx)
+find_func(char_u *name, int is_global, cctx_T *cctx)
{
- ufunc_T *fp = find_func_even_dead(name, cctx);
+ ufunc_T *fp = find_func_even_dead(name, is_global, cctx);
if (fp != NULL && (fp->uf_flags & FC_DEAD) == 0)
return fp;
return current_funccal;
}
+/*
+ * Mark all functions of script "sid" as deleted.
+ */
+ void
+delete_script_functions(int sid)
+{
+ hashitem_T *hi;
+ ufunc_T *fp;
+ long_u todo;
+ char buf[30];
+ size_t len;
+
+ buf[0] = K_SPECIAL;
+ buf[1] = KS_EXTRA;
+ buf[2] = (int)KE_SNR;
+ sprintf(buf + 3, "%d_", sid);
+ len = STRLEN(buf);
+
+ todo = func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0; ++hi)
+ if (!HASHITEM_EMPTY(hi))
+ {
+ if (STRNCMP(fp->uf_name, buf, len) == 0)
+ {
+ fp = HI2UF(hi);
+ fp->uf_flags |= FC_DEAD;
+ func_clear(fp, TRUE);
+ }
+ --todo;
+ }
+}
+
#if defined(EXITFREE) || defined(PROTO)
void
free_all_functions(void)
* User defined function.
*/
if (fp == NULL)
- fp = find_func(rfname, NULL);
+ fp = find_func(rfname, FALSE, NULL);
// Trigger FuncUndefined event, may load the function.
if (fp == NULL
&& apply_autocmds(EVENT_FUNCUNDEFINED,
- rfname, rfname, TRUE, NULL)
+ rfname, rfname, TRUE, NULL)
&& !aborting())
{
// executed an autocommand, search for the function again
- fp = find_func(rfname, NULL);
+ fp = find_func(rfname, FALSE, NULL);
}
// Try loading a package.
if (fp == NULL && script_autoload(rfname, TRUE) && !aborting())
{
// loaded a package, search for the function again
- fp = find_func(rfname, NULL);
+ fp = find_func(rfname, FALSE, NULL);
}
if (fp == NULL)
{
// If using Vim9 script try not local to the script.
// TODO: should not do this if the name started with "s:".
if (p != NULL)
- fp = find_func(p, NULL);
+ fp = find_func(p, FALSE, NULL);
}
if (fp != NULL && (fp->uf_flags & FC_DELETED))
* Get a function name, translating "<SID>" and "<SNR>".
* Also handles a Funcref in a List or Dictionary.
* Returns the function name in allocated memory, or NULL for failure.
+ * Set "*is_global" to TRUE when the function must be global, unless
+ * "is_global" is NULL.
* flags:
* TFN_INT: internal function name OK
* TFN_QUIET: be quiet
char_u *
trans_function_name(
char_u **pp,
+ int *is_global,
int skip, // only find the end, don't evaluate
int flags,
funcdict_T *fdp, // return: info about dictionary used
{
// skip over "s:" and "g:"
if (lead == 2 || (lv.ll_name[0] == 'g' && lv.ll_name[1] == ':'))
+ {
+ if (is_global != NULL && lv.ll_name[0] == 'g')
+ *is_global = TRUE;
lv.ll_name += 2;
+ }
len = (int)(end - lv.ll_name);
}
int saved_did_emsg;
int saved_wait_return = need_wait_return;
char_u *name = NULL;
+ int is_global = FALSE;
char_u *p;
char_u *arg;
char_u *line_arg = NULL;
* g:func global function name, same as "func"
*/
p = eap->arg;
- name = trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL);
+ name = trans_function_name(&p, &is_global, eap->skip,
+ TFN_NO_AUTOLOAD, &fudi, NULL);
paren = (vim_strchr(p, '(') != NULL);
if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip)
{
*p = NUL;
if (!eap->skip && !got_int)
{
- fp = find_func(name, NULL);
+ fp = find_func(name, is_global, NULL);
if (fp == NULL && ASCII_ISUPPER(*eap->arg))
{
char_u *up = untrans_function_name(name);
// With Vim9 script the name was made script-local, if not
// found try again with the original name.
if (up != NULL)
- fp = find_func(up, NULL);
+ fp = find_func(up, FALSE, NULL);
}
if (fp != NULL)
{
if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL)
emsg(_(e_funcdict));
- else if (name != NULL && find_func(name, NULL) != NULL)
+ else if (name != NULL && find_func(name, is_global, NULL) != NULL)
emsg_funcname(e_funcexts, name);
}
if (*p == '!')
p = skipwhite(p + 1);
p += eval_fname_script(p);
- vim_free(trans_function_name(&p, TRUE, 0, NULL, NULL));
+ vim_free(trans_function_name(&p, NULL, TRUE, 0, NULL, NULL));
if (*skipwhite(p) == '(')
{
if (nesting == MAX_FUNC_NESTING - 1)
goto erret;
}
- fp = find_func_even_dead(name, NULL);
+ fp = find_func_even_dead(name, is_global, NULL);
if (fp != NULL)
{
int dead = fp->uf_flags & FC_DEAD;
fp->uf_varargs = varargs;
if (sandbox)
flags |= FC_SANDBOX;
+ if (in_vim9script() && !ASCII_ISUPPER(*fp->uf_name))
+ flags |= FC_VIM9;
fp->uf_flags = flags;
fp->uf_calls = 0;
fp->uf_cleared = FALSE;
}
int
-translated_function_exists(char_u *name)
+translated_function_exists(char_u *name, int is_global)
{
if (builtin_function(name, -1))
return has_internal_func(name);
- return find_func(name, NULL) != NULL;
+ return find_func(name, is_global, NULL) != NULL;
}
/*
char_u *p;
int n = FALSE;
int flag;
+ int is_global = FALSE;
flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD;
if (no_deref)
flag |= TFN_NO_DEREF;
- p = trans_function_name(&nm, FALSE, flag, NULL, NULL);
+ p = trans_function_name(&nm, &is_global, FALSE, flag, NULL, NULL);
nm = skipwhite(nm);
// Only accept "funcname", "funcname ", "funcname (..." and
// "funcname(...", not "funcname!...".
if (p != NULL && (*nm == NUL || *nm == '('))
- n = translated_function_exists(p);
+ n = translated_function_exists(p, is_global);
vim_free(p);
return n;
}
{
char_u *nm = name;
char_u *p;
+ int is_global = FALSE;
- p = trans_function_name(&nm, FALSE, TFN_INT|TFN_QUIET, NULL, NULL);
+ p = trans_function_name(&nm, &is_global, FALSE,
+ TFN_INT|TFN_QUIET, NULL, NULL);
- if (p != NULL && *nm == NUL)
- if (!check || translated_function_exists(p))
- return p;
+ if (p != NULL && *nm == NUL
+ && (!check || translated_function_exists(p, is_global)))
+ return p;
vim_free(p);
return NULL;
char_u *p;
char_u *name;
funcdict_T fudi;
+ int is_global = FALSE;
p = eap->arg;
- name = trans_function_name(&p, eap->skip, 0, &fudi, NULL);
+ name = trans_function_name(&p, &is_global, eap->skip, 0, &fudi, NULL);
vim_free(fudi.fd_newkey);
if (name == NULL)
{
*p = NUL;
if (!eap->skip)
- fp = find_func(name, NULL);
+ fp = find_func(name, is_global, NULL);
vim_free(name);
if (!eap->skip)
semsg(_("E131: Cannot delete function %s: It is in use"), eap->arg);
return;
}
+ if (fp->uf_flags & FC_VIM9)
+ {
+ semsg(_("E1084: Cannot delete Vim9 script function %s"), eap->arg);
+ return;
+ }
if (fudi.fd_dict != NULL)
{
if (name == NULL || !func_name_refcount(name))
return;
- fp = find_func(name, NULL);
+ fp = find_func(name, FALSE, NULL);
if (fp == NULL && isdigit(*name))
{
#ifdef EXITFREE
if (name == NULL || !func_name_refcount(name))
return;
- fp = find_func(name, NULL);
+ fp = find_func(name, FALSE, NULL);
if (fp != NULL)
++fp->uf_refcount;
else if (isdigit(*name))
return;
}
- tofree = trans_function_name(&arg, eap->skip, TFN_INT, &fudi, &partial);
+ tofree = trans_function_name(&arg, NULL, eap->skip,
+ TFN_INT, &fudi, &partial);
if (fudi.fd_newkey != NULL)
{
// Still need to give an error message for missing key.
: rettv->vval.v_partial->pt_name;
// Translate "s:func" to the stored function name.
fname = fname_trans_sid(fname, fname_buf, &tofree, &error);
- fp = find_func(fname, NULL);
+ fp = find_func(fname, FALSE, NULL);
vim_free(tofree);
}
if (fp_in == NULL)
{
fname = fname_trans_sid(name, fname_buf, &tofree, &error);
- fp = find_func(fname, NULL);
+ fp = find_func(fname, FALSE, NULL);
}
if (fp != NULL)
{
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 650,
/**/
649,
/**/
static int
generate_funcref(cctx_T *cctx, char_u *name)
{
- ufunc_T *ufunc = find_func(name, cctx);
+ ufunc_T *ufunc = find_func(name, FALSE, cctx);
if (ufunc == NULL)
return FAIL;
}
// If we can find the function by name generate the right call.
- ufunc = find_func(name, cctx);
+ ufunc = find_func(name, FALSE, cctx);
if (ufunc != NULL)
{
res = generate_CALL(cctx, ufunc, argcount);
return call_bfunc(func_idx, argcount, ectx);
}
- ufunc = find_func(name, NULL);
+ ufunc = find_func(name, FALSE, NULL);
if (ufunc != NULL)
return call_ufunc(ufunc, argcount, ectx, iptr);
int current;
int line_idx = 0;
int prev_current = 0;
+ int is_global = FALSE;
- fname = trans_function_name(&arg, FALSE,
+ fname = trans_function_name(&arg, &is_global, FALSE,
TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL, NULL);
if (fname == NULL)
{
return;
}
- ufunc = find_func(fname, NULL);
+ ufunc = find_func(fname, is_global, NULL);
if (ufunc == NULL)
{
char_u *p = untrans_function_name(fname);
if (p != NULL)
// Try again without making it script-local.
- ufunc = find_func(p, NULL);
+ ufunc = find_func(p, FALSE, NULL);
}
vim_free(fname);
if (ufunc == NULL)
funcname[1] = KS_EXTRA;
funcname[2] = (int)KE_SNR;
sprintf((char *)funcname + 3, "%ld_%s", (long)sid, name);
- *ufunc = find_func(funcname, NULL);
+ *ufunc = find_func(funcname, FALSE, NULL);
if (funcname != buffer)
vim_free(funcname);