# HG changeset patch # User Bram Moolenaar # Date 1600283705 -7200 # Node ID 07e48ee8c3bb67239e9459fa9037918098bcc802 # Parent 3d2dc70119a76fa7aaa4f5da3c766a5382d12d7c patch 8.2.1698: cannot lock a variable in legacy Vim script like in Vim9 Commit: https://github.com/vim/vim/commit/a187c43cfe8863d48b2159d695fedcb71f8525c1 Author: Bram Moolenaar Date: Wed Sep 16 21:08:28 2020 +0200 patch 8.2.1698: cannot lock a variable in legacy Vim script like in Vim9 Problem: Cannot lock a variable in legacy Vim script like in Vim9. Solution: Make ":lockvar 0" work. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -12364,7 +12364,9 @@ text... < is equivalent to: > :let x = 1 :lockvar! x -< This is useful if you want to make sure the variable +< NOTE: in Vim9 script `:const` works differently, see + |vim9-const| + This is useful if you want to make sure the variable is not modified. If the value is a List or Dictionary literal then the items also cannot be changed: > const ll = [1, 2, 3] @@ -12404,6 +12406,8 @@ text... [depth] is relevant when locking a |List| or |Dictionary|. It specifies how deep the locking goes: + 0 Lock the variable {name} but not its + value. 1 Lock the |List| or |Dictionary| itself, cannot add or remove items, but can still change their values. @@ -12417,7 +12421,14 @@ text... |Dictionary|, one level deeper. The default [depth] is 2, thus when {name} is a |List| or |Dictionary| the values cannot be changed. - *E743* + + Example with [depth] 0: > + let mylist = [1, 2, 3] + lockvar 0 mylist + let mylist[0] = 77 " OK + call add(mylist, 4] " OK + let mylist = [7, 8, 9] " Error! +< *E743* For unlimited depth use [!] and omit [depth]. However, there is a maximum depth of 100 to catch loops. diff --git a/src/dict.c b/src/dict.c --- a/src/dict.c +++ b/src/dict.c @@ -1009,7 +1009,7 @@ dict_extend(dict_T *d1, dict_T *d2, char } else if (*action == 'f' && HI2DI(hi2) != di1) { - if (var_check_lock(di1->di_tv.v_lock, arg_errmsg, TRUE) + if (value_check_lock(di1->di_tv.v_lock, arg_errmsg, TRUE) || var_check_ro(di1->di_flags, arg_errmsg, TRUE)) break; clear_tv(&di1->di_tv); @@ -1227,7 +1227,7 @@ dict_remove(typval_T *argvars, typval_T if (argvars[2].v_type != VAR_UNKNOWN) semsg(_(e_toomanyarg), "remove()"); else if ((d = argvars[0].vval.v_dict) != NULL - && !var_check_lock(d->dv_lock, arg_errmsg, TRUE)) + && !value_check_lock(d->dv_lock, arg_errmsg, TRUE)) { key = tv_get_string_chk(&argvars[1]); if (key != NULL) diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -1055,7 +1055,8 @@ get_lval( } // existing variable, need to check if it can be changed else if ((flags & GLV_READ_ONLY) == 0 - && var_check_ro(lp->ll_di->di_flags, name, FALSE)) + && (var_check_ro(lp->ll_di->di_flags, name, FALSE) + || var_check_lock(lp->ll_di->di_flags, name, FALSE))) { clear_tv(&var1); return NULL; @@ -1220,7 +1221,7 @@ set_var_lval( semsg(_(e_letwrong), op); return; } - if (var_check_lock(lp->ll_blob->bv_lock, lp->ll_name, FALSE)) + if (value_check_lock(lp->ll_blob->bv_lock, lp->ll_name, FALSE)) return; if (lp->ll_range && rettv->v_type == VAR_BLOB) @@ -1297,7 +1298,7 @@ set_var_lval( } *endp = cc; } - else if (var_check_lock(lp->ll_newkey == NULL + else if (value_check_lock(lp->ll_newkey == NULL ? lp->ll_tv->v_lock : lp->ll_tv->vval.v_dict->dv_lock, lp->ll_name, FALSE)) ; @@ -1317,7 +1318,7 @@ set_var_lval( */ for (ri = rettv->vval.v_list->lv_first; ri != NULL && ll_li != NULL; ) { - if (var_check_lock(ll_li->li_tv.v_lock, lp->ll_name, FALSE)) + if (value_check_lock(ll_li->li_tv.v_lock, lp->ll_name, FALSE)) return; ri = ri->li_next; if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == ll_n1)) diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -1560,9 +1560,9 @@ do_unlet_var( *name_end = cc; } else if ((lp->ll_list != NULL - && var_check_lock(lp->ll_list->lv_lock, lp->ll_name, FALSE)) + && value_check_lock(lp->ll_list->lv_lock, lp->ll_name, FALSE)) || (lp->ll_dict != NULL - && var_check_lock(lp->ll_dict->dv_lock, lp->ll_name, FALSE))) + && value_check_lock(lp->ll_dict->dv_lock, lp->ll_name, FALSE))) return FAIL; else if (lp->ll_range) { @@ -1573,7 +1573,7 @@ do_unlet_var( while (ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= ll_n1)) { li = ll_li->li_next; - if (var_check_lock(ll_li->li_tv.v_lock, lp->ll_name, FALSE)) + if (value_check_lock(ll_li->li_tv.v_lock, lp->ll_name, FALSE)) return FAIL; ll_li = li; ++ll_n1; @@ -1646,7 +1646,7 @@ do_unlet(char_u *name, int forceit) di = HI2DI(hi); if (var_check_fixed(di->di_flags, name, FALSE) || var_check_ro(di->di_flags, name, FALSE) - || var_check_lock(d->dv_lock, name, FALSE)) + || value_check_lock(d->dv_lock, name, FALSE)) return FAIL; delete_var(ht, hi); @@ -1677,9 +1677,6 @@ do_lock_var( int cc; dictitem_T *di; - if (deep == 0) // nothing to do - return OK; - if (lp->ll_tv == NULL) { cc = *name_end; @@ -1710,11 +1707,16 @@ do_lock_var( di->di_flags |= DI_FLAGS_LOCK; else di->di_flags &= ~DI_FLAGS_LOCK; - item_lock(&di->di_tv, deep, lock, FALSE); + if (deep != 0) + item_lock(&di->di_tv, deep, lock, FALSE); } } *name_end = cc; } + else if (deep == 0) + { + // nothing to do + } else if (lp->ll_range) { listitem_T *li = lp->ll_li; @@ -3007,8 +3009,13 @@ set_var_const( goto failed; } + // Check in this order for backwards compatibility: + // - Whether the variable is read-only + // - Whether the variable value is locked + // - Whether the variable is locked if (var_check_ro(di->di_flags, name, FALSE) - || var_check_lock(di->di_tv.v_lock, name, FALSE)) + || value_check_lock(di->di_tv.v_lock, name, FALSE) + || var_check_lock(di->di_flags, name, FALSE)) goto failed; } else @@ -3158,6 +3165,22 @@ var_check_ro(int flags, char_u *name, in } /* + * Return TRUE if di_flags "flags" indicates variable "name" is locked. + * Also give an error message. + */ + int +var_check_lock(int flags, char_u *name, int use_gettext) +{ + if (flags & DI_FLAGS_LOCK) + { + semsg(_(e_variable_is_locked_str), + use_gettext ? (char_u *)_(name) : name); + return TRUE; + } + return FALSE; +} + +/* * Return TRUE if di_flags "flags" indicates variable "name" is fixed. * Also give an error message. */ @@ -3204,12 +3227,12 @@ var_wrong_func_name( } /* - * Return TRUE if "flags" indicates variable "name" is locked (immutable). - * Also give an error message, using "name" or _("name") when use_gettext is - * TRUE. + * Return TRUE if "flags" indicates variable "name" has a locked (immutable) + * value. Also give an error message, using "name" or _("name") when + * "use_gettext" is TRUE. */ int -var_check_lock(int lock, char_u *name, int use_gettext) +value_check_lock(int lock, char_u *name, int use_gettext) { if (lock & VAR_LOCKED) { diff --git a/src/list.c b/src/list.c --- a/src/list.c +++ b/src/list.c @@ -817,7 +817,7 @@ f_flatten(typval_T *argvars, typval_T *r } l = argvars[0].vval.v_list; - if (l != NULL && !var_check_lock(l->lv_lock, + if (l != NULL && !value_check_lock(l->lv_lock, (char_u *)N_("flatten() argument"), TRUE) && list_flatten(l, maxdepth) == OK) copy_tv(&argvars[0], rettv); @@ -1439,7 +1439,7 @@ list_remove(typval_T *argvars, typval_T int idx; if ((l = argvars[0].vval.v_list) == NULL - || var_check_lock(l->lv_lock, arg_errmsg, TRUE)) + || value_check_lock(l->lv_lock, arg_errmsg, TRUE)) return; idx = (long)tv_get_number_chk(&argvars[1], &error); @@ -1687,7 +1687,7 @@ do_sort_uniq(typval_T *argvars, typval_T else { l = argvars[0].vval.v_list; - if (l == NULL || var_check_lock(l->lv_lock, + if (l == NULL || value_check_lock(l->lv_lock, (char_u *)(sort ? N_("sort() argument") : N_("uniq() argument")), TRUE)) goto theend; @@ -1955,13 +1955,13 @@ filter_map(typval_T *argvars, typval_T * else if (argvars[0].v_type == VAR_LIST) { if ((l = argvars[0].vval.v_list) == NULL - || (!map && var_check_lock(l->lv_lock, arg_errmsg, TRUE))) + || (!map && value_check_lock(l->lv_lock, arg_errmsg, TRUE))) return; } else if (argvars[0].v_type == VAR_DICT) { if ((d = argvars[0].vval.v_dict) == NULL - || (!map && var_check_lock(d->dv_lock, arg_errmsg, TRUE))) + || (!map && value_check_lock(d->dv_lock, arg_errmsg, TRUE))) return; } else @@ -2004,7 +2004,7 @@ filter_map(typval_T *argvars, typval_T * --todo; di = HI2DI(hi); - if (map && (var_check_lock(di->di_tv.v_lock, + if (map && (value_check_lock(di->di_tv.v_lock, arg_errmsg, TRUE) || var_check_ro(di->di_flags, arg_errmsg, TRUE))) @@ -2077,7 +2077,7 @@ filter_map(typval_T *argvars, typval_T * l->lv_lock = VAR_LOCKED; for (li = l->lv_first; li != NULL; li = nli) { - if (map && var_check_lock(li->li_tv.v_lock, arg_errmsg, TRUE)) + if (map && value_check_lock(li->li_tv.v_lock, arg_errmsg, TRUE)) break; nli = li->li_next; set_vim_var_nr(VV_KEY, idx); @@ -2131,7 +2131,7 @@ f_add(typval_T *argvars, typval_T *rettv if (argvars[0].v_type == VAR_LIST) { if ((l = argvars[0].vval.v_list) != NULL - && !var_check_lock(l->lv_lock, + && !value_check_lock(l->lv_lock, (char_u *)N_("add() argument"), TRUE) && list_append_tv(l, &argvars[1]) == OK) copy_tv(&argvars[0], rettv); @@ -2139,7 +2139,7 @@ f_add(typval_T *argvars, typval_T *rettv else if (argvars[0].v_type == VAR_BLOB) { if ((b = argvars[0].vval.v_blob) != NULL - && !var_check_lock(b->bv_lock, + && !value_check_lock(b->bv_lock, (char_u *)N_("add() argument"), TRUE)) { int error = FALSE; @@ -2282,7 +2282,7 @@ f_extend(typval_T *argvars, typval_T *re l1 = argvars[0].vval.v_list; l2 = argvars[1].vval.v_list; - if (l1 != NULL && !var_check_lock(l1->lv_lock, arg_errmsg, TRUE) + if (l1 != NULL && !value_check_lock(l1->lv_lock, arg_errmsg, TRUE) && l2 != NULL) { if (argvars[2].v_type != VAR_UNKNOWN) @@ -2318,7 +2318,7 @@ f_extend(typval_T *argvars, typval_T *re d1 = argvars[0].vval.v_dict; d2 = argvars[1].vval.v_dict; - if (d1 != NULL && !var_check_lock(d1->dv_lock, arg_errmsg, TRUE) + if (d1 != NULL && !value_check_lock(d1->dv_lock, arg_errmsg, TRUE) && d2 != NULL) { // Check the third argument. @@ -2402,7 +2402,7 @@ f_insert(typval_T *argvars, typval_T *re else if (argvars[0].v_type != VAR_LIST) semsg(_(e_listblobarg), "insert()"); else if ((l = argvars[0].vval.v_list) != NULL - && !var_check_lock(l->lv_lock, + && !value_check_lock(l->lv_lock, (char_u *)N_("insert() argument"), TRUE)) { if (argvars[2].v_type != VAR_UNKNOWN) @@ -2475,7 +2475,7 @@ f_reverse(typval_T *argvars, typval_T *r if (argvars[0].v_type != VAR_LIST) semsg(_(e_listblobarg), "reverse()"); else if ((l = argvars[0].vval.v_list) != NULL - && !var_check_lock(l->lv_lock, + && !value_check_lock(l->lv_lock, (char_u *)N_("reverse() argument"), TRUE)) { if (l->lv_first == &range_list_item) diff --git a/src/proto/evalvars.pro b/src/proto/evalvars.pro --- a/src/proto/evalvars.pro +++ b/src/proto/evalvars.pro @@ -68,9 +68,10 @@ void vars_clear_ext(hashtab_T *ht, int f void set_var(char_u *name, typval_T *tv, int copy); void set_var_const(char_u *name, type_T *type, typval_T *tv_arg, int copy, int flags); int var_check_ro(int flags, char_u *name, int use_gettext); +int var_check_lock(int flags, char_u *name, int use_gettext); int var_check_fixed(int flags, char_u *name, int use_gettext); int var_wrong_func_name(char_u *name, int new_var); -int var_check_lock(int lock, char_u *name, int use_gettext); +int value_check_lock(int lock, char_u *name, int use_gettext); int valid_varname(char_u *varname); void reset_v_option_vars(void); void assert_error(garray_T *gap); diff --git a/src/testdir/test_const.vim b/src/testdir/test_const.vim --- a/src/testdir/test_const.vim +++ b/src/testdir/test_const.vim @@ -215,6 +215,14 @@ func Test_lockvar() if 0 | lockvar x | endif let x = 'again' + + let val = [1, 2, 3] + lockvar 0 val + let val[0] = 9 + call assert_equal([9, 2, 3], val) + call add(val, 4) + call assert_equal([9, 2, 3, 4], val) + call assert_fails('let val = [4, 5, 6]', 'E1122:') endfunc diff --git a/src/testdir/test_listdict.vim b/src/testdir/test_listdict.vim --- a/src/testdir/test_listdict.vim +++ b/src/testdir/test_listdict.vim @@ -354,7 +354,7 @@ endfunc " Locked variables func Test_list_locked_var() let expected = [ - \ [['0000-000', 'ppppppp'], + \ [['1000-000', 'ppppppF'], \ ['0000-000', 'ppppppp'], \ ['0000-000', 'ppppppp']], \ [['1000-000', 'ppppppF'], @@ -381,7 +381,7 @@ func Test_list_locked_var() exe "unlockvar " . depth . " l" endif let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]") - call assert_equal(expected[depth][u][0], ps) + call assert_equal(expected[depth][u][0], ps, 'depth: ' .. depth) let ps = '' try let l[1][1][0] = 99 @@ -425,7 +425,7 @@ func Test_list_locked_var() catch let ps .= 'F' endtry - call assert_equal(expected[depth][u][1], ps) + call assert_equal(expected[depth][u][1], ps, 'depth: ' .. depth) endfor endfor call assert_fails("let x=islocked('a b')", 'E488:') @@ -438,7 +438,7 @@ endfunc " Unletting locked variables func Test_list_locked_var_unlet() let expected = [ - \ [['0000-000', 'ppppppp'], + \ [['1000-000', 'ppppppp'], \ ['0000-000', 'ppppppp'], \ ['0000-000', 'ppppppp']], \ [['1000-000', 'ppFppFp'], @@ -466,7 +466,7 @@ func Test_list_locked_var_unlet() exe "unlockvar " . depth . " l" endif let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]") - call assert_equal(expected[depth][u][0], ps) + call assert_equal(expected[depth][u][0], ps, 'depth: ' .. depth) let ps = '' try unlet l[2]['6'][7] @@ -666,6 +666,9 @@ func Test_func_arg_list() call s:arg_list_test(1, 2, [3, 4], {5: 6}) endfunc +func Test_dict_item_locked() +endfunc + " Tests for reverse(), sort(), uniq() func Test_reverse_sort_uniq() let l = ['-0', 'A11', 2, 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5] diff --git a/src/typval.c b/src/typval.c --- a/src/typval.c +++ b/src/typval.c @@ -512,8 +512,8 @@ tv_check_lock(typval_T *tv, char_u *name default: break; } - return var_check_lock(tv->v_lock, name, use_gettext) - || (lock != 0 && var_check_lock(lock, name, use_gettext)); + return value_check_lock(tv->v_lock, name, use_gettext) + || (lock != 0 && value_check_lock(lock, name, use_gettext)); } /* diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -3346,11 +3346,11 @@ def_function(exarg_T *eap, char_u *name_ if (fudi.fd_di == NULL) { // Can't add a function to a locked dictionary - if (var_check_lock(fudi.fd_dict->dv_lock, eap->arg, FALSE)) + if (value_check_lock(fudi.fd_dict->dv_lock, eap->arg, FALSE)) goto erret; } // Can't change an existing function if it is locked - else if (var_check_lock(fudi.fd_di->di_tv.v_lock, eap->arg, FALSE)) + else if (value_check_lock(fudi.fd_di->di_tv.v_lock, eap->arg, FALSE)) goto erret; // Give the function a sequential number. Can only be used with a diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1698, +/**/ 1697, /**/ 1696,