vim-patch:8.2.0602: :unlet $VAR does not work properly (#13238)

Problem:    :unlet $VAR does not work properly.
Solution:   Make ":lockvar $VAR" fail.  Check the "skip" flag.
7e0868efcf

Include patch 8.2.0601 changes so that ex_unletlock() can execute a callback if there are no errors.
This commit is contained in:
Sean Dewar 2020-12-02 13:44:13 +00:00 committed by GitHub
parent d80f262f89
commit 8fb786e415
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 96 additions and 55 deletions

View File

@ -64,6 +64,7 @@ static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary");
static char *e_illvar = N_("E461: Illegal variable name: %s");
static char *e_cannot_mod = N_("E995: Cannot modify existing variable");
static char *e_invalwindow = N_("E957: Invalid window number");
static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
// TODO(ZyX-I): move to eval/executor
static char *e_letwrong = N_("E734: Wrong variable type for %s=");
@ -2638,19 +2639,15 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx)
xp->xp_pattern = arg;
}
/*
* ":unlet[!] var1 ... " command.
*/
/// ":unlet[!] var1 ... " command.
void ex_unlet(exarg_T *eap)
{
ex_unletlock(eap, eap->arg, 0);
ex_unletlock(eap, eap->arg, 0, do_unlet_var);
}
// TODO(ZyX-I): move to eval/ex_cmds
/*
* ":lockvar" and ":unlockvar" commands
*/
/// ":lockvar" and ":unlockvar" commands
void ex_lockvar(exarg_T *eap)
{
char_u *arg = eap->arg;
@ -2663,15 +2660,23 @@ void ex_lockvar(exarg_T *eap)
arg = skipwhite(arg);
}
ex_unletlock(eap, arg, deep);
ex_unletlock(eap, arg, deep, do_lock_var);
}
// TODO(ZyX-I): move to eval/ex_cmds
/*
* ":unlet", ":lockvar" and ":unlockvar" are quite similar.
*/
static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep)
/// Common parsing logic for :unlet, :lockvar and :unlockvar.
///
/// Invokes `callback` afterwards if successful and `eap->skip == false`.
///
/// @param[in] eap Ex command arguments for the command.
/// @param[in] argstart Start of the string argument for the command.
/// @param[in] deep Levels to (un)lock for :(un)lockvar, -1 to (un)lock
/// everything.
/// @param[in] callback Appropriate handler for the command.
static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep,
ex_unletlock_callback callback)
FUNC_ATTR_NONNULL_ALL
{
char_u *arg = argstart;
char_u *name_end;
@ -2680,13 +2685,16 @@ static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep)
do {
if (*arg == '$') {
const char *name = (char *)++arg;
lv.ll_name = (const char *)arg;
lv.ll_tv = NULL;
arg++;
if (get_env_len((const char_u **)&arg) == 0) {
EMSG2(_(e_invarg2), name - 1);
EMSG2(_(e_invarg2), arg - 1);
return;
}
os_unsetenv(name);
if (!error && !eap->skip && callback(&lv, arg, eap, deep) == FAIL) {
error = true;
}
name_end = arg;
} else {
// Parse the name and find the end.
@ -2707,18 +2715,9 @@ static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep)
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) == 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);
@ -2732,8 +2731,19 @@ static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep)
// TODO(ZyX-I): move to eval/ex_cmds
static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit)
/// Unlet a variable indicated by `lp`.
///
/// @param[in] lp The lvalue.
/// @param[in] name_end End of the string argument for the command.
/// @param[in] eap Ex command arguments for :unlet.
/// @param[in] deep Unused.
///
/// @return OK on success, or FAIL on failure.
static int do_unlet_var(lval_T *lp, char_u *name_end, exarg_T *eap,
int deep FUNC_ATTR_UNUSED)
FUNC_ATTR_NONNULL_ALL
{
int forceit = eap->forceit;
int ret = OK;
int cc;
@ -2741,8 +2751,10 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit)
cc = *name_end;
*name_end = NUL;
// Normal name or expanded name.
if (do_unlet(lp->ll_name, lp->ll_name_len, forceit) == FAIL) {
// Environment variable, normal name or expanded name.
if (*lp->ll_name == '$') {
os_unsetenv(lp->ll_name + 1);
} else if (do_unlet(lp->ll_name, lp->ll_name_len, forceit) == FAIL) {
ret = FAIL;
}
*name_end = cc;
@ -2816,7 +2828,7 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit)
///
/// @param[in] name Variable name to unlet.
/// @param[in] name_len Variable name length.
/// @param[in] fonceit If true, do not complain if variable doesnt exist.
/// @param[in] forceit If true, do not complain if variable doesnt exist.
///
/// @return OK if it existed, FAIL otherwise.
int do_unlet(const char *const name, const size_t name_len, const bool forceit)
@ -2883,14 +2895,21 @@ int do_unlet(const char *const name, const size_t name_len, const bool forceit)
// TODO(ZyX-I): move to eval/ex_cmds
/*
* Lock or unlock variable indicated by "lp".
* "deep" is the levels to go (-1 for unlimited);
* "lock" is TRUE for ":lockvar", FALSE for ":unlockvar".
*/
static int do_lock_var(lval_T *lp, char_u *const name_end, const int deep,
const bool lock)
/// Lock or unlock variable indicated by `lp`.
///
/// Locks if `eap->cmdidx == CMD_lockvar`, unlocks otherwise.
///
/// @param[in] lp The lvalue.
/// @param[in] name_end Unused.
/// @param[in] eap Ex command arguments for :(un)lockvar.
/// @param[in] deep Levels to (un)lock, -1 to (un)lock everything.
///
/// @return OK on success, or FAIL on failure.
static int do_lock_var(lval_T *lp, char_u *name_end FUNC_ATTR_UNUSED,
exarg_T *eap, int deep)
FUNC_ATTR_NONNULL_ARG(1, 3)
{
bool lock = eap->cmdidx == CMD_lockvar;
int ret = OK;
if (deep == 0) { // Nothing to do.
@ -2898,6 +2917,10 @@ static int do_lock_var(lval_T *lp, char_u *const name_end, const int deep,
}
if (lp->ll_tv == NULL) {
if (*lp->ll_name == '$') {
EMSG2(_(e_lock_unlock), lp->ll_name);
ret = FAIL;
} else {
// Normal name or expanded name.
dictitem_T *const di = find_var(
(const char *)lp->ll_name, lp->ll_name_len, NULL,
@ -2909,7 +2932,8 @@ static int do_lock_var(lval_T *lp, char_u *const name_end, const int deep,
&& di->di_tv.v_type != VAR_LIST) {
// For historical reasons this error is not given for Lists and
// Dictionaries. E.g. b: dictionary may be locked/unlocked.
emsgf(_("E940: Cannot lock or unlock variable %s"), lp->ll_name);
EMSG2(_(e_lock_unlock), lp->ll_name);
ret = FAIL;
} else {
if (lock) {
di->di_flags |= DI_FLAGS_LOCK;
@ -2918,6 +2942,7 @@ static int do_lock_var(lval_T *lp, char_u *const name_end, const int deep,
}
tv_item_lock(&di->di_tv, deep, lock);
}
}
} else if (lp->ll_range) {
listitem_T *li = lp->ll_li;

View File

@ -233,6 +233,8 @@ typedef enum {
kDictListItems, ///< List dictionary contents: [keys, values].
} DictListType;
typedef int (*ex_unletlock_callback)(lval_T *, char_u *, exarg_T *, int);
// Used for checking if local variables or arguments used in a lambda.
extern bool *eval_lavars_used;

View File

@ -1393,6 +1393,20 @@ func Test_compound_assignment_operators()
let @/ = ''
endfunc
func Test_unlet_env()
let $TESTVAR = 'yes'
call assert_equal('yes', $TESTVAR)
call assert_fails('lockvar $TESTVAR', 'E940')
call assert_fails('unlockvar $TESTVAR', 'E940')
call assert_equal('yes', $TESTVAR)
if 0
unlet $TESTVAR
endif
call assert_equal('yes', $TESTVAR)
unlet $TESTVAR
call assert_equal('', $TESTVAR)
endfunc
func Test_funccall_garbage_collect()
func Func(x, ...)
call add(a:x, a:000)