# HG changeset patch # User Bram Moolenaar # Date 1628876703 -7200 # Node ID 6f13d9ea0d04c0569ebf433bc8e475956db1c91f # Parent 5ad1d3061d62ca6761a241a232c2b8da2d4daf27 patch 8.2.3339: Vim9: cannot lock a member in a local dict Commit: https://github.com/vim/vim/commit/aacc966c5d0ed91e33ed32b08f17cf4df3ca1394 Author: Bram Moolenaar Date: Fri Aug 13 19:40:51 2021 +0200 patch 8.2.3339: Vim9: cannot lock a member in a local dict Problem: Vim9: cannot lock a member in a local dict. Solution: Get the local dict from the stack and pass it to get_lval(). diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -902,17 +902,26 @@ get_lval( if ((*p != '[' && *p != '.') || lp->ll_name == NULL) return p; - cc = *p; - *p = NUL; - // When we would write to the variable pass &ht and prevent autoload. - writing = !(flags & GLV_READ_ONLY); - v = find_var(lp->ll_name, writing ? &ht : NULL, + if (in_vim9script() && lval_root != NULL) + { + // using local variable + lp->ll_tv = lval_root; + } + else + { + cc = *p; + *p = NUL; + // When we would write to the variable pass &ht and prevent autoload. + writing = !(flags & GLV_READ_ONLY); + v = find_var(lp->ll_name, writing ? &ht : NULL, (flags & GLV_NO_AUTOLOAD) || writing); - if (v == NULL && !quiet) - semsg(_(e_undefined_variable_str), lp->ll_name); - *p = cc; - if (v == NULL) - return NULL; + if (v == NULL && !quiet) + semsg(_(e_undefined_variable_str), lp->ll_name); + *p = cc; + if (v == NULL) + return NULL; + lp->ll_tv = &v->di_tv; + } if (in_vim9script() && (flags & GLV_NO_DECL) == 0) { @@ -924,7 +933,6 @@ get_lval( /* * Loop until no more [idx] or .key is following. */ - lp->ll_tv = &v->di_tv; var1.v_type = VAR_UNKNOWN; var2.v_type = VAR_UNKNOWN; while (*p == '[' || (*p == '.' && p[1] != '=' && p[1] != '.')) diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -1835,6 +1835,8 @@ EXTERN int timer_busy INIT(= 0); // w #endif #ifdef FEAT_EVAL EXTERN int input_busy INIT(= 0); // when inside get_user_input() then > 0 + +EXTERN typval_T *lval_root INIT(= NULL); #endif #ifdef FEAT_BEVAL_TERM diff --git a/src/testdir/test_vim9_cmd.vim b/src/testdir/test_vim9_cmd.vim --- a/src/testdir/test_vim9_cmd.vim +++ b/src/testdir/test_vim9_cmd.vim @@ -1195,6 +1195,23 @@ def Test_lockvar() s:theList[1] = 44 assert_equal([1, 44, 3], s:theList) + var d = {a: 1, b: 2} + d.a = 3 + d.b = 4 + assert_equal({a: 3, b: 4}, d) + lockvar d.a + d.b = 5 + var ex = '' + try + d.a = 6 + catch + ex = v:exception + endtry + assert_match('E1121:', ex) + unlockvar d.a + d.a = 7 + assert_equal({a: 7, b: 5}, d) + var lines =<< trim END vim9script var theList = [1, 2, 3] 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 @@ -588,6 +588,25 @@ def Test_disassemble_unlet() res) enddef +def s:LockLocal() + var d = {a: 1} + lockvar d.a +enddef + +def Test_disassemble_locl_local() + var res = execute('disass s:LockLocal') + assert_match('\d*_LockLocal\_s*' .. + 'var d = {a: 1}\_s*' .. + '\d PUSHS "a"\_s*' .. + '\d PUSHNR 1\_s*' .. + '\d NEWDICT size 1\_s*' .. + '\d STORE $0\_s*' .. + 'lockvar d.a\_s*' .. + '\d LOAD $0\_s*' .. + '\d LOCKUNLOCK lockvar d.a\_s*', + res) +enddef + def s:ScriptFuncTry() try echo "yes" diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -756,6 +756,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3339, +/**/ 3338, /**/ 3337, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -70,6 +70,7 @@ typedef enum { ISN_UNLETINDEX, // unlet item of list or dict ISN_UNLETRANGE, // unlet items of list + ISN_LOCKUNLOCK, // :lock and :unlock for local variable member ISN_LOCKCONST, // lock constant value // constants diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -2262,12 +2262,12 @@ generate_PUT(cctx_T *cctx, int regname, } static int -generate_EXEC(cctx_T *cctx, char_u *line) +generate_EXEC(cctx_T *cctx, isntype_T isntype, char_u *line) { isn_T *isn; RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_EXEC)) == NULL) + if ((isn = generate_instr(cctx, isntype)) == NULL) return FAIL; isn->isn_arg.string = vim_strsave(line); return OK; @@ -7426,6 +7426,7 @@ compile_lock_unlock( int ret = OK; size_t len; char_u *buf; + isntype_T isn = ISN_EXEC; if (cctx->ctx_skip == SKIP_YES) return OK; @@ -7437,8 +7438,19 @@ compile_lock_unlock( if (lookup_local(p, end - p, NULL, cctx) == OK) { - emsg(_(e_cannot_lock_unlock_local_variable)); - return FAIL; + char_u *s = p; + + if (*end != '.' && *end != '[') + { + emsg(_(e_cannot_lock_unlock_local_variable)); + return FAIL; + } + + // For "d.member" put the local variable on the stack, it will be + // passed to ex_lockvar() indirectly. + if (compile_load(&s, end, cctx, FALSE, FALSE) == FAIL) + return FAIL; + isn = ISN_LOCKUNLOCK; } } @@ -7453,7 +7465,7 @@ compile_lock_unlock( vim_snprintf((char *)buf, len, "%s %s", eap->cmdidx == CMD_lockvar ? "lockvar" : "unlockvar", p); - ret = generate_EXEC(cctx, buf); + ret = generate_EXEC(cctx, isn, buf); vim_free(buf); *name_end = cc; @@ -9110,7 +9122,7 @@ compile_exec(char_u *line_arg, exarg_T * generate_EXECCONCAT(cctx, count); } else - generate_EXEC(cctx, line); + generate_EXEC(cctx, ISN_EXEC, line); theend: if (*nextcmd != NUL) @@ -10198,6 +10210,7 @@ delete_instr(isn_T *isn) case ISN_LOADOPT: case ISN_LOADT: case ISN_LOADW: + case ISN_LOCKUNLOCK: case ISN_PUSHEXC: case ISN_PUSHFUNC: case ISN_PUSHS: diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1396,6 +1396,27 @@ fill_partial_and_closure(partial_T *pt, return OK; } +/* + * Execute iptr->isn_arg.string as an Ex command. + */ + static int +exec_command(isn_T *iptr) +{ + source_cookie_T cookie; + + SOURCING_LNUM = iptr->isn_lnum; + // Pass getsourceline to get an error for a missing ":end" + // command. + CLEAR_FIELD(cookie); + cookie.sourcing_lnum = iptr->isn_lnum - 1; + if (do_cmdline(iptr->isn_arg.string, + getsourceline, &cookie, + DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED) == FAIL + || did_emsg) + return FAIL; + return OK; +} + // used for v_instr of typval of VAR_INSTR struct instr_S { ectx_T *instr_ectx; @@ -1637,21 +1658,8 @@ exec_instructions(ectx_T *ectx) { // execute Ex command line case ISN_EXEC: - { - source_cookie_T cookie; - - SOURCING_LNUM = iptr->isn_lnum; - // Pass getsourceline to get an error for a missing ":end" - // command. - CLEAR_FIELD(cookie); - cookie.sourcing_lnum = iptr->isn_lnum - 1; - if (do_cmdline(iptr->isn_arg.string, - getsourceline, &cookie, - DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED) - == FAIL - || did_emsg) - goto on_error; - } + if (exec_command(iptr) == FAIL) + goto on_error; break; // execute Ex command line split at NL characters. @@ -2880,6 +2888,23 @@ exec_instructions(ectx_T *ectx) vim_unsetenv(iptr->isn_arg.unlet.ul_name); break; + case ISN_LOCKUNLOCK: + { + typval_T *lval_root_save = lval_root; + int res; + + // Stack has the local variable, argument the whole :lock + // or :unlock command, like ISN_EXEC. + --ectx->ec_stack.ga_len; + lval_root = STACK_TV_BOT(0); + res = exec_command(iptr); + clear_tv(lval_root); + lval_root = lval_root_save; + if (res == FAIL) + goto on_error; + } + break; + case ISN_LOCKCONST: item_lock(STACK_TV_BOT(-1), 100, TRUE, TRUE); break; @@ -5244,6 +5269,9 @@ list_instructions(char *pfx, isn_T *inst case ISN_UNLETRANGE: smsg("%s%4d UNLETRANGE", pfx, current); break; + case ISN_LOCKUNLOCK: + smsg("%s%4d LOCKUNLOCK %s", pfx, current, iptr->isn_arg.string); + break; case ISN_LOCKCONST: smsg("%s%4d LOCKCONST", pfx, current); break;