0
0
mirror of https://github.com/vim/vim.git synced 2025-07-26 11:04:33 -04:00

patch 8.2.0601: Vim9: :unlet is not compiled

Problem:    Vim9: :unlet is not compiled.
Solution:   Implement :unlet instruction and check for errors.
This commit is contained in:
Bram Moolenaar 2020-04-19 16:28:59 +02:00
parent d3aac2917d
commit d72c1bf0a6
10 changed files with 232 additions and 35 deletions

View 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 == '{')
{

View 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)
if (!error && !eap->skip
&& callback(&lv, name_end, eap, deep, cookie) == FAIL)
error = TRUE;
}
else
{
if (do_lock_var(&lv, name_end, deep,
eap->cmdidx == CMD_lockvar) == 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;

View 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);

View 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);

View 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'

View 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.

View File

@ -746,6 +746,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
601,
/**/
600,
/**/

View 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;
};

View 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;

View 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));