summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2020-04-19 16:28:59 +0200
committerBram Moolenaar <Bram@vim.org>2020-04-19 16:28:59 +0200
commitd72c1bf0a6784afdc8d8ceab4a007cd76d5b81e1 (patch)
treea7e9a7f882a81830fa5225db9f4057d3eb13615c
parentd3aac2917db38f8590648ee76eebfa178fc4c069 (diff)
downloadvim-git-d72c1bf0a6784afdc8d8ceab4a007cd76d5b81e1.tar.gz
patch 8.2.0601: Vim9: :unlet is not compiledv8.2.0601
Problem: Vim9: :unlet is not compiled. Solution: Implement :unlet instruction and check for errors.
-rw-r--r--src/eval.c8
-rw-r--r--src/evalvars.c54
-rw-r--r--src/proto/evalvars.pro1
-rw-r--r--src/proto/vim9compile.pro3
-rw-r--r--src/testdir/test_vim9_disassemble.vim19
-rw-r--r--src/testdir/test_vim9_script.vim58
-rw-r--r--src/version.c2
-rw-r--r--src/vim9.h9
-rw-r--r--src/vim9compile.c102
-rw-r--r--src/vim9execute.c11
10 files changed, 232 insertions, 35 deletions
diff --git a/src/eval.c b/src/eval.c
index 1840f7420..94ecf936e 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -5098,6 +5098,7 @@ find_name_end(
int br_nest = 0;
char_u *p;
int len;
+ int vim9script = current_sctx.sc_version == SCRIPT_VERSION_VIM9;
if (expr_start != NULL)
{
@@ -5106,12 +5107,13 @@ find_name_end(
}
// Quick check for valid starting character.
- if ((flags & FNE_CHECK_START) && !eval_isnamec1(*arg) && *arg != '{')
+ if ((flags & FNE_CHECK_START) && !eval_isnamec1(*arg)
+ && (*arg != '{' || vim9script))
return arg;
for (p = arg; *p != NUL
&& (eval_isnamec(*p)
- || *p == '{'
+ || (*p == '{' && !vim9script)
|| ((flags & FNE_INCL_BR) && (*p == '[' || *p == '.'))
|| mb_nest != 0
|| br_nest != 0); MB_PTR_ADV(p))
@@ -5151,7 +5153,7 @@ find_name_end(
--br_nest;
}
- if (br_nest == 0)
+ if (br_nest == 0 && !vim9script)
{
if (*p == '{')
{
diff --git a/src/evalvars.c b/src/evalvars.c
index 5dddd0903..1952eb88f 100644
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -172,9 +172,8 @@ static void list_win_vars(int *first);
static void list_tab_vars(int *first);
static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first);
static char_u *ex_let_one(char_u *arg, typval_T *tv, int copy, int flags, char_u *endchars, char_u *op);
-static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep);
-static int do_unlet_var(lval_T *lp, char_u *name_end, int forceit);
-static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock);
+static int do_unlet_var(lval_T *lp, char_u *name_end, exarg_T *eap, int deep, void *cookie);
+static int do_lock_var(lval_T *lp, char_u *name_end, exarg_T *eap, int deep, void *cookie);
static void item_lock(typval_T *tv, int deep, int lock);
static void delete_var(hashtab_T *ht, hashitem_T *hi);
static void list_one_var(dictitem_T *v, char *prefix, int *first);
@@ -1372,7 +1371,7 @@ ex_let_one(
void
ex_unlet(exarg_T *eap)
{
- ex_unletlock(eap, eap->arg, 0);
+ ex_unletlock(eap, eap->arg, 0, 0, do_unlet_var, NULL);
}
/*
@@ -1392,17 +1391,22 @@ ex_lockvar(exarg_T *eap)
arg = skipwhite(arg);
}
- ex_unletlock(eap, arg, deep);
+ ex_unletlock(eap, arg, deep, 0, do_lock_var, NULL);
}
/*
* ":unlet", ":lockvar" and ":unlockvar" are quite similar.
+ * Also used for Vim9 script. "callback" is invoked as:
+ * callback(&lv, name_end, eap, deep, cookie)
*/
- static void
+ void
ex_unletlock(
exarg_T *eap,
char_u *argstart,
- int deep)
+ int deep,
+ int glv_flags,
+ int (*callback)(lval_T *, char_u *, exarg_T *, int, void *),
+ void *cookie)
{
char_u *arg = argstart;
char_u *name_end;
@@ -1426,8 +1430,8 @@ ex_unletlock(
}
// Parse the name and find the end.
- name_end = get_lval(arg, NULL, &lv, TRUE, eap->skip || error, 0,
- FNE_CHECK_START);
+ name_end = get_lval(arg, NULL, &lv, TRUE, eap->skip || error,
+ glv_flags, FNE_CHECK_START);
if (lv.ll_name == NULL)
error = TRUE; // error but continue parsing
if (name_end == NULL || (!VIM_ISWHITE(*name_end)
@@ -1443,26 +1447,15 @@ ex_unletlock(
break;
}
- if (!error && !eap->skip)
- {
- if (eap->cmdidx == CMD_unlet)
- {
- if (do_unlet_var(&lv, name_end, eap->forceit) == FAIL)
- error = TRUE;
- }
- else
- {
- if (do_lock_var(&lv, name_end, deep,
- eap->cmdidx == CMD_lockvar) == FAIL)
- error = TRUE;
- }
- }
+ if (!error && !eap->skip
+ && callback(&lv, name_end, eap, deep, cookie) == FAIL)
+ error = TRUE;
if (!eap->skip)
clear_lval(&lv);
arg = skipwhite(name_end);
- } while (!ends_excmd(*arg));
+ } while (!ends_excmd2(name_end, arg));
eap->nextcmd = check_nextcmd(arg);
}
@@ -1471,8 +1464,11 @@ ex_unletlock(
do_unlet_var(
lval_T *lp,
char_u *name_end,
- int forceit)
+ exarg_T *eap,
+ int deep UNUSED,
+ void *cookie UNUSED)
{
+ int forceit = eap->forceit;
int ret = OK;
int cc;
@@ -1541,6 +1537,10 @@ do_unlet(char_u *name, int forceit)
dict_T *d;
dictitem_T *di;
+ if (current_sctx.sc_version == SCRIPT_VERSION_VIM9
+ && check_vim9_unlet(name) == FAIL)
+ return FAIL;
+
ht = find_var_ht(name, &varname);
if (ht != NULL && *varname != NUL)
{
@@ -1592,9 +1592,11 @@ do_unlet(char_u *name, int forceit)
do_lock_var(
lval_T *lp,
char_u *name_end,
+ exarg_T *eap,
int deep,
- int lock)
+ void *cookie UNUSED)
{
+ int lock = eap->cmdidx == CMD_lockvar;
int ret = OK;
int cc;
dictitem_T *di;
diff --git a/src/proto/evalvars.pro b/src/proto/evalvars.pro
index c53c115bf..e4159328d 100644
--- a/src/proto/evalvars.pro
+++ b/src/proto/evalvars.pro
@@ -21,6 +21,7 @@ char_u *skip_var_list(char_u *arg, int include_type, int *var_count, int *semico
void list_hashtable_vars(hashtab_T *ht, char *prefix, int empty, int *first);
void ex_unlet(exarg_T *eap);
void ex_lockvar(exarg_T *eap);
+void ex_unletlock(exarg_T *eap, char_u *argstart, int deep, int glv_flags, int (*callback)(lval_T *, char_u *, exarg_T *, int, void *), void *cookie);
int do_unlet(char_u *name, int forceit);
void del_menutrans_vars(void);
char_u *get_user_var_name(expand_T *xp, int idx);
diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro
index b22bf98bf..76a9c9992 100644
--- a/src/proto/vim9compile.pro
+++ b/src/proto/vim9compile.pro
@@ -1,13 +1,14 @@
/* vim9compile.c */
int check_defined(char_u *p, int len, cctx_T *cctx);
char_u *skip_type(char_u *start);
-type_T *parse_type(char_u **arg, garray_T *type_list);
+type_T *parse_type(char_u **arg, garray_T *type_gap);
char *vartype_name(vartype_T type);
char *type_name(type_T *type, char **tofree);
int get_script_item_idx(int sid, char_u *name, int check_writable);
imported_T *find_imported(char_u *name, size_t len, cctx_T *cctx);
char_u *to_name_const_end(char_u *arg);
int assignment_len(char_u *p, int *heredoc);
+int check_vim9_unlet(char_u *name);
void compile_def_function(ufunc_T *ufunc, int set_return_type);
void delete_instr(isn_T *isn);
void delete_def_function(ufunc_T *ufunc);
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index 13d954262..7f6d86a56 100644
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -126,6 +126,25 @@ def Test_disassemble_store()
res)
enddef
+def s:ScriptFuncUnlet()
+ g:somevar = "value"
+ unlet g:somevar
+ unlet! g:somevar
+enddef
+
+def Test_disassemble_unlet()
+ let res = execute('disass s:ScriptFuncUnlet')
+ assert_match('<SNR>\d*_ScriptFuncUnlet.*' ..
+ 'g:somevar = "value".*' ..
+ '\d PUSHS "value".*' ..
+ '\d STOREG g:somevar.*' ..
+ 'unlet g:somevar.*' ..
+ '\d UNLET g:somevar.*' ..
+ 'unlet! g:somevar.*' ..
+ '\d UNLET! g:somevar.*',
+ res)
+enddef
+
def s:ScriptFuncTry()
try
echo 'yes'
diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim
index 15ac0fc9d..568338b27 100644
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -213,7 +213,7 @@ def Mess(): string
return 'xxx'
enddef
-func Test_assignment_failure()
+def Test_assignment_failure()
call CheckDefFailure(['let var=234'], 'E1004:')
call CheckDefFailure(['let var =234'], 'E1004:')
call CheckDefFailure(['let var= 234'], 'E1004:')
@@ -241,9 +241,6 @@ func Test_assignment_failure()
call CheckDefFailure(['let xnr += 4'], 'E1020:')
call CheckScriptFailure(['vim9script', 'def Func()', 'let dummy = s:notfound', 'enddef'], 'E1050:')
- " TODO: implement this error
- "call CheckScriptFailure(['vim9script', 'let svar = 123', 'unlet svar'], 'E1050:')
- "call CheckScriptFailure(['vim9script', 'let svar = 123', 'unlet s:svar'], 'E1050:')
call CheckDefFailure(['let var: list<string> = [123]'], 'expected list<string> but got list<number>')
call CheckDefFailure(['let var: list<number> = ["xx"]'], 'expected list<number> but got list<string>')
@@ -259,7 +256,40 @@ func Test_assignment_failure()
call assert_fails('s/^/\=Mess()/n', 'E794:')
call CheckDefFailure(['let var: dict<number'], 'E1009:')
-endfunc
+enddef
+
+def Test_unlet()
+ g:somevar = 'yes'
+ assert_true(exists('g:somevar'))
+ unlet g:somevar
+ assert_false(exists('g:somevar'))
+ unlet! g:somevar
+
+ call CheckScriptFailure([
+ 'vim9script',
+ 'let svar = 123',
+ 'unlet svar',
+ ], 'E1081:')
+ call CheckScriptFailure([
+ 'vim9script',
+ 'let svar = 123',
+ 'unlet s:svar',
+ ], 'E1081:')
+ call CheckScriptFailure([
+ 'vim9script',
+ 'let svar = 123',
+ 'def Func()',
+ ' unlet svar',
+ 'enddef',
+ ], 'E1081:')
+ call CheckScriptFailure([
+ 'vim9script',
+ 'let svar = 123',
+ 'def Func()',
+ ' unlet s:svar',
+ 'enddef',
+ ], 'E1081:')
+enddef
func Test_wrong_type()
call CheckDefFailure(['let var: list<nothing>'], 'E1010:')
@@ -1155,6 +1185,24 @@ def Test_vim9_comment_not_compiled()
au! TabEnter
unlet g:entered
+
+ CheckScriptSuccess([
+ 'vim9script',
+ 'let g:var = 123',
+ 'let w:var = 777',
+ 'unlet g:var w:var # something',
+ ])
+
+ CheckScriptFailure([
+ 'vim9script',
+ 'let g:var = 123',
+ 'unlet g:var# comment',
+ ], 'E108:')
+
+ CheckScriptFailure([
+ 'let g:var = 123',
+ 'unlet g:var # something',
+ ], 'E488:')
enddef
" Keep this last, it messes up highlighting.
diff --git a/src/version.c b/src/version.c
index d4a1f4fb0..21400bb15 100644
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 601,
+/**/
600,
/**/
599,
diff --git a/src/vim9.h b/src/vim9.h
index d5d4fd9be..64749ce1b 100644
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -44,6 +44,8 @@ typedef enum {
ISN_STORENR, // store number into local variable isn_arg.storenr.stnr_idx
+ ISN_UNLET, // unlet variable isn_arg.unlet.ul_name
+
// constants
ISN_PUSHNR, // push number isn_arg.number
ISN_PUSHBOOL, // push bool value isn_arg.number
@@ -205,6 +207,12 @@ typedef struct {
int script_idx; // index in sn_var_vals
} script_T;
+// arguments to ISN_UNLET
+typedef struct {
+ char_u *ul_name; // variable name with g:, w:, etc.
+ int ul_forceit; // forceit flag
+} unlet_T;
+
/*
* Instruction
*/
@@ -235,6 +243,7 @@ struct isn_S {
storeopt_T storeopt;
loadstore_T loadstore;
script_T script;
+ unlet_T unlet;
} isn_arg;
};
diff --git a/src/vim9compile.c b/src/vim9compile.c
index c99d39602..c4a5c08d8 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -987,6 +987,23 @@ generate_LOADV(
}
/*
+ * Generate an ISN_UNLET instruction.
+ */
+ static int
+generate_UNLET(cctx_T *cctx, char_u *name, int forceit)
+{
+ isn_T *isn;
+
+ RETURN_OK_IF_SKIP(cctx);
+ if ((isn = generate_instr(cctx, ISN_UNLET)) == NULL)
+ return FAIL;
+ isn->isn_arg.unlet.ul_name = vim_strsave(name);
+ isn->isn_arg.unlet.ul_forceit = forceit;
+
+ return OK;
+}
+
+/*
* Generate an ISN_LOADS instruction.
*/
static int
@@ -4543,6 +4560,81 @@ theend:
}
/*
+ * Check if "name" can be "unlet".
+ */
+ int
+check_vim9_unlet(char_u *name)
+{
+ if (name[1] != ':' || vim_strchr((char_u *)"gwtb", *name) == NULL)
+ {
+ semsg(_("E1081: Cannot unlet %s"), name);
+ return FAIL;
+ }
+ return OK;
+}
+
+/*
+ * Callback passed to ex_unletlock().
+ */
+ static int
+compile_unlet(
+ lval_T *lvp,
+ char_u *name_end,
+ exarg_T *eap,
+ int deep UNUSED,
+ void *coookie)
+{
+ cctx_T *cctx = coookie;
+
+ if (lvp->ll_tv == NULL)
+ {
+ char_u *p = lvp->ll_name;
+ int cc = *name_end;
+ int ret = OK;
+
+ // Normal name. Only supports g:, w:, t: and b: namespaces.
+ *name_end = NUL;
+ if (check_vim9_unlet(p) == FAIL)
+ ret = FAIL;
+ else
+ ret = generate_UNLET(cctx, p, eap->forceit);
+
+ *name_end = cc;
+ return ret;
+ }
+
+ // TODO: unlet {list}[idx]
+ // TODO: unlet {dict}[key]
+ emsg("Sorry, :unlet not fully implemented yet");
+ return FAIL;
+}
+
+/*
+ * compile "unlet var", "lock var" and "unlock var"
+ * "arg" points to "var".
+ */
+ static char_u *
+compile_unletlock(char_u *arg, exarg_T *eap, cctx_T *cctx)
+{
+ char_u *p = arg;
+
+ if (eap->cmdidx != CMD_unlet)
+ {
+ emsg("Sorry, :lock and unlock not implemented yet");
+ return NULL;
+ }
+
+ if (*p == '!')
+ {
+ p = skipwhite(p + 1);
+ eap->forceit = TRUE;
+ }
+
+ ex_unletlock(eap, p, 0, GLV_NO_AUTOLOAD, compile_unlet, cctx);
+ return eap->nextcmd == NULL ? (char_u *)"" : eap->nextcmd;
+}
+
+/*
* Compile an :import command.
*/
static char_u *
@@ -6031,6 +6123,12 @@ compile_def_function(ufunc_T *ufunc, int set_return_type)
line = compile_assignment(p, &ea, ea.cmdidx, &cctx);
break;
+ case CMD_unlet:
+ case CMD_unlockvar:
+ case CMD_lockvar:
+ line = compile_unletlock(p, &ea, &cctx);
+ break;
+
case CMD_import:
line = compile_import(p, &cctx);
break;
@@ -6264,6 +6362,10 @@ delete_instr(isn_T *isn)
vim_free(isn->isn_arg.loadstore.ls_name);
break;
+ case ISN_UNLET:
+ vim_free(isn->isn_arg.unlet.ul_name);
+ break;
+
case ISN_STOREOPT:
vim_free(isn->isn_arg.storeopt.so_name);
break;
diff --git a/src/vim9execute.c b/src/vim9execute.c
index b0e35b6f3..e6758698c 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -1068,6 +1068,12 @@ call_def_function(
}
break;
+ case ISN_UNLET:
+ if (do_unlet(iptr->isn_arg.unlet.ul_name,
+ iptr->isn_arg.unlet.ul_forceit) == FAIL)
+ goto failed;
+ break;
+
// create a list from items on the stack; uses a single allocation
// for the list header and the items
case ISN_NEWLIST:
@@ -2108,6 +2114,11 @@ ex_disassemble(exarg_T *eap)
case ISN_PUSHEXC:
smsg("%4d PUSH v:exception", current);
break;
+ case ISN_UNLET:
+ smsg("%4d UNLET%s %s", current,
+ iptr->isn_arg.unlet.ul_forceit ? "!" : "",
+ iptr->isn_arg.unlet.ul_name);
+ break;
case ISN_NEWLIST:
smsg("%4d NEWLIST size %lld", current,
(long long)(iptr->isn_arg.number));