changeset 20091:a64c16ff98b8 v8.2.0601

patch 8.2.0601: Vim9: :unlet is not compiled Commit: https://github.com/vim/vim/commit/d72c1bf0a6784afdc8d8ceab4a007cd76d5b81e1 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Apr 19 16:28:59 2020 +0200 patch 8.2.0601: Vim9: :unlet is not compiled Problem: Vim9: :unlet is not compiled. Solution: Implement :unlet instruction and check for errors.
author Bram Moolenaar <Bram@vim.org>
date Sun, 19 Apr 2020 16:30:04 +0200
parents b2225a10b777
children 7e38221cd2c1
files src/eval.c src/evalvars.c src/proto/evalvars.pro src/proto/vim9compile.pro src/testdir/test_vim9_disassemble.vim src/testdir/test_vim9_script.vim src/version.c src/vim9.h src/vim9compile.c src/vim9execute.c
diffstat 10 files changed, 232 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- 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 == '{')
 	    {
--- 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;
--- a/src/proto/evalvars.pro
+++ b/src/proto/evalvars.pro
@@ -21,6 +21,7 @@ char_u *skip_var_list(char_u *arg, int i
 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);
--- 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);
--- 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'
--- 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.
--- 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,
--- 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;
 };
 
--- 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
 		    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;
--- 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));