]> granicus.if.org Git - vim/commitdiff
patch 8.2.0601: Vim9: :unlet is not compiled v8.2.0601
authorBram Moolenaar <Bram@vim.org>
Sun, 19 Apr 2020 14:28:59 +0000 (16:28 +0200)
committerBram Moolenaar <Bram@vim.org>
Sun, 19 Apr 2020 14:28:59 +0000 (16:28 +0200)
Problem:    Vim9: :unlet is not compiled.
Solution:   Implement :unlet instruction and check for errors.

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

index 1840f7420c5a084a0307b296920c1aaebfb8d613..94ecf936e428180c3c7cd67c0f6345d08e6796ec 100644 (file)
@@ -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 == '{')
            {
index 5dddd0903f73ee905e2bf20715b1ba684e60c9d5..1952eb88f4361e1d0894281f7e820a402fca6ee7 100644 (file)
@@ -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;
index c53c115bf1c6b6477520c988446c6ff836bcb5f5..e4159328dc16a6791c23f22465120ba1022a84b4 100644 (file)
@@ -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);
index b22bf98bf69a0383af473c85f164c59a1ea6a49e..76a9c9992285a6122e671be67700278da2404f23 100644 (file)
@@ -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);
index 13d9542622835a9de7e072a1e08278992652866c..7f6d86a56e0cd459245f1ac4a4e0e637bc313af5 100644 (file)
@@ -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'
index 15ac0fc9d27be63069da7369d05b9941a53e8840..568338b27b35af0f4c81bfc7011297655cdb39d2 100644 (file)
@@ -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.
index d4a1f4fb0f254a2533253129f07e73d97b522e8f..21400bb15eca0de72cab252882a14c4869967690 100644 (file)
@@ -746,6 +746,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    601,
 /**/
     600,
 /**/
index d5d4fd9be4cf345bc0607500026ca1bc15e1b1b4..64749ce1b51fd8ffb1cfb98491d7356216fb6111 100644 (file)
@@ -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;
 };
 
index c99d39602ed8bd091e509c55f980df41aeb8f8cf..c4a5c08d89d58e3e1b72f8b91ea717bd72a3f524 100644 (file)
@@ -986,6 +986,23 @@ generate_LOADV(
     return generate_LOAD(cctx, ISN_LOADV, vidx, NULL, type);
 }
 
+/*
+ * 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.
  */
@@ -4542,6 +4559,81 @@ theend:
     return ret;
 }
 
+/*
+ * 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.
  */
@@ -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;
index b0e35b6f36d3147d2f0dab4a79b71ae390006f1d..e6758698c4bedd88d6ca9a28d889cc97de5971cc 100644 (file)
@@ -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));