# HG changeset patch # User Bram Moolenaar # Date 1587306604 -7200 # Node ID a64c16ff98b8096afbe914a07ca70267980713b7 # Parent b2225a10b77755b07e603cf255e1f744c5b2281a patch 8.2.0601: Vim9: :unlet is not compiled Commit: https://github.com/vim/vim/commit/d72c1bf0a6784afdc8d8ceab4a007cd76d5b81e1 Author: Bram Moolenaar 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. diff --git a/src/eval.c b/src/eval.c --- 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 --- 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 --- 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); diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro --- 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 --- 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('\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 --- 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 = [123]'], 'expected list but got list') call CheckDefFailure(['let var: list = ["xx"]'], 'expected list but got list') @@ -259,7 +256,40 @@ func Test_assignment_failure() call assert_fails('s/^/\=Mess()/n', 'E794:') call CheckDefFailure(['let var: dict'], '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 --- 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 --- 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 --- 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; diff --git a/src/vim9execute.c b/src/vim9execute.c --- 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));