To: vim_dev@googlegroups.com Subject: Patch 8.2.1473 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.1473 Problem: Items in a list given to :const can still be modified. Solution: Work like ":lockvar! name" but don't lock referenced items. Make locking a blob work. Files: runtime/doc/eval.txt, src/evalvars.c, src/eval.c, src/testdir/test_const.vim *** ../vim-8.2.1472/runtime/doc/eval.txt 2020-08-15 18:38:32.072814243 +0200 --- runtime/doc/eval.txt 2020-08-17 20:39:11.291181532 +0200 *************** *** 12201,12210 **** :const x = 1 < is equivalent to: > :let x = 1 ! :lockvar 1 x < This is useful if you want to make sure the variable ! is not modified. ! *E995* |:const| does not allow to for changing a variable: > :let x = 1 :const x = 2 " Error! --- 12264,12281 ---- :const x = 1 < is equivalent to: > :let x = 1 ! :lockvar! x < 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] ! let ll[1] = 5 " Error! ! < Nested references are not locked: > ! let lvar = ['a'] ! const lconst = [0, lvar] ! let lconst[0] = 2 " Error! ! let lconst[1][0] = 'b' " OK ! < *E995* |:const| does not allow to for changing a variable: > :let x = 1 :const x = 2 " Error! *** ../vim-8.2.1472/src/evalvars.c 2020-08-16 22:49:57.393356184 +0200 --- src/evalvars.c 2020-08-17 20:41:59.898237787 +0200 *************** *** 173,179 **** static char_u *ex_let_one(char_u *arg, typval_T *tv, int copy, int flags, char_u *endchars, char_u *op); 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); static void list_one_var_a(char *prefix, char_u *name, int type, char_u *string, int *first); --- 173,179 ---- static char_u *ex_let_one(char_u *arg, typval_T *tv, int copy, int flags, char_u *endchars, char_u *op); 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, int check_refcount); static void delete_var(hashtab_T *ht, hashitem_T *hi); static void list_one_var(dictitem_T *v, char *prefix, int *first); static void list_one_var_a(char *prefix, char_u *name, int type, char_u *string, int *first); *************** *** 1703,1709 **** di->di_flags |= DI_FLAGS_LOCK; else di->di_flags &= ~DI_FLAGS_LOCK; ! item_lock(&di->di_tv, deep, lock); } } *name_end = cc; --- 1703,1709 ---- di->di_flags |= DI_FLAGS_LOCK; else di->di_flags &= ~DI_FLAGS_LOCK; ! item_lock(&di->di_tv, deep, lock, FALSE); } } *name_end = cc; *************** *** 1715,1740 **** // (un)lock a range of List items. while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { ! item_lock(&li->li_tv, deep, lock); li = li->li_next; ++lp->ll_n1; } } else if (lp->ll_list != NULL) // (un)lock a List item. ! item_lock(&lp->ll_li->li_tv, deep, lock); else // (un)lock a Dictionary item. ! item_lock(&lp->ll_di->di_tv, deep, lock); return ret; } /* * Lock or unlock an item. "deep" is nr of levels to go. */ static void ! item_lock(typval_T *tv, int deep, int lock) { static int recurse = 0; list_T *l; --- 1715,1742 ---- // (un)lock a range of List items. while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { ! item_lock(&li->li_tv, deep, lock, FALSE); li = li->li_next; ++lp->ll_n1; } } else if (lp->ll_list != NULL) // (un)lock a List item. ! item_lock(&lp->ll_li->li_tv, deep, lock, FALSE); else // (un)lock a Dictionary item. ! item_lock(&lp->ll_di->di_tv, deep, lock, FALSE); return ret; } /* * Lock or unlock an item. "deep" is nr of levels to go. + * When "check_refcount" is TRUE do not lock a list or dict with a reference + * count larger than 1. */ static void ! item_lock(typval_T *tv, int deep, int lock, int check_refcount) { static int recurse = 0; list_T *l; *************** *** 1776,1782 **** break; case VAR_BLOB: ! if ((b = tv->vval.v_blob) != NULL) { if (lock) b->bv_lock |= VAR_LOCKED; --- 1778,1785 ---- break; case VAR_BLOB: ! if ((b = tv->vval.v_blob) != NULL ! && !(check_refcount && b->bv_refcount > 1)) { if (lock) b->bv_lock |= VAR_LOCKED; *************** *** 1785,1791 **** } break; case VAR_LIST: ! if ((l = tv->vval.v_list) != NULL) { if (lock) l->lv_lock |= VAR_LOCKED; --- 1788,1795 ---- } break; case VAR_LIST: ! if ((l = tv->vval.v_list) != NULL ! && !(check_refcount && l->lv_refcount > 1)) { if (lock) l->lv_lock |= VAR_LOCKED; *************** *** 1794,1804 **** if ((deep < 0 || deep > 1) && l->lv_first != &range_list_item) // recursive: lock/unlock the items the List contains FOR_ALL_LIST_ITEMS(l, li) ! item_lock(&li->li_tv, deep - 1, lock); } break; case VAR_DICT: ! if ((d = tv->vval.v_dict) != NULL) { if (lock) d->dv_lock |= VAR_LOCKED; --- 1798,1809 ---- if ((deep < 0 || deep > 1) && l->lv_first != &range_list_item) // recursive: lock/unlock the items the List contains FOR_ALL_LIST_ITEMS(l, li) ! item_lock(&li->li_tv, deep - 1, lock, check_refcount); } break; case VAR_DICT: ! if ((d = tv->vval.v_dict) != NULL ! && !(check_refcount && d->dv_refcount > 1)) { if (lock) d->dv_lock |= VAR_LOCKED; *************** *** 1813,1819 **** if (!HASHITEM_EMPTY(hi)) { --todo; ! item_lock(&HI2DI(hi)->di_tv, deep - 1, lock); } } } --- 1818,1825 ---- if (!HASHITEM_EMPTY(hi)) { --todo; ! item_lock(&HI2DI(hi)->di_tv, deep - 1, lock, ! check_refcount); } } } *************** *** 3087,3093 **** } if (flags & LET_IS_CONST) ! item_lock(&di->di_tv, 1, TRUE); } /* --- 3093,3102 ---- } if (flags & LET_IS_CONST) ! // Like :lockvar! name: lock the value and what it contains, but only ! // if the reference count is up to one. That locks only literal ! // values. ! item_lock(&di->di_tv, DICT_MAXNEST, TRUE, TRUE); } /* *** ../vim-8.2.1472/src/eval.c 2020-08-16 18:42:50.678811797 +0200 --- src/eval.c 2020-08-17 20:58:12.160440884 +0200 *************** *** 1218,1223 **** --- 1218,1225 ---- semsg(_(e_letwrong), op); return; } + if (var_check_lock(lp->ll_blob->bv_lock, lp->ll_name, FALSE)) + return; if (lp->ll_range && rettv->v_type == VAR_BLOB) { *** ../vim-8.2.1472/src/testdir/test_const.vim 2020-08-16 22:49:57.393356184 +0200 --- src/testdir/test_const.vim 2020-08-17 20:47:56.420170904 +0200 *************** *** 273,292 **** call assert_fails('const {s2} = "bar"', 'E995:') endfunc ! func Test_lock_depth_is_1() ! const l = [1, 2, 3] ! const d = {'foo': 10} ! ! " Modify list - setting item is OK, adding/removing items not ! let l[0] = 42 call assert_fails('call add(l, 4)', 'E741:') call assert_fails('unlet l[1]', 'E741:') ! " Modify dict - changing item is OK, adding/removing items not ! let d['foo'] = 'hello' ! let d.foo = 44 call assert_fails("let d['bar'] = 'hello'", 'E741:') call assert_fails("unlet d['foo']", 'E741:') endfunc " vim: shiftwidth=2 sts=2 expandtab --- 273,307 ---- call assert_fails('const {s2} = "bar"', 'E995:') endfunc ! func Test_lock_depth_is_2() ! " Modify list - error when changing item or adding/removing items ! const l = [1, 2, [3, 4]] ! call assert_fails('let l[0] = 42', 'E741:') ! call assert_fails('let l[2][0] = 42', 'E741:') call assert_fails('call add(l, 4)', 'E741:') call assert_fails('unlet l[1]', 'E741:') ! " Modify blob - error when changing ! const b = 0z001122 ! call assert_fails('let b[0] = 42', 'E741:') ! ! " Modify dict - error when changing item or adding/removing items ! const d = {'foo': 10} ! call assert_fails("let d['foo'] = 'hello'", 'E741:') ! call assert_fails("let d.foo = 'hello'", 'E741:') call assert_fails("let d['bar'] = 'hello'", 'E741:') call assert_fails("unlet d['foo']", 'E741:') + + " Modifying list or dict item contents is OK. + let lvar = ['a', 'b'] + let bvar = 0z1122 + const l2 = [0, lvar, bvar] + let l2[1][0] = 'c' + let l2[2][1] = 0x33 + call assert_equal([0, ['c', 'b'], 0z1133], l2) + + const d2 = #{a: 0, b: lvar, c: 4} + let d2.b[1] = 'd' endfunc " vim: shiftwidth=2 sts=2 expandtab *** ../vim-8.2.1472/src/version.c 2020-08-17 19:34:06.961427079 +0200 --- src/version.c 2020-08-17 19:56:12.945187908 +0200 *************** *** 756,757 **** --- 756,759 ---- { /* Add new patch number below this line */ + /**/ + 1473, /**/ -- hundred-and-one symptoms of being an internet addict: 225. You sign up for free subscriptions for all the computer magazines /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///