diff --git a/src/eval.c b/src/eval.c index edef8785d5f04b0771ceb33bf4c53b58a8d47fda..0dd2058872da266a93c77a755f0e28f522cde9c3 100644 --- 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 index ebf236b0003360014ed2cb36ad4c21118505c5b4..f09ad482fbf7dc737e0bff7a84f7c42f5a594851 100644 --- a/src/globals.h +++ b/src/globals.h @@ -1835,6 +1835,8 @@ EXTERN int timer_busy INIT(= 0); // when timer is inside vgetc() then > 0 #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 index 84cb6d665057739d9c0ea118696345e13cf47465..7ec43e9d58c298ceb04b7341b1e0ae86f9faddef 100644 --- 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 index 1d1730e00426ecc6036f68b80e4e0a2b44eb3e13..15a68dd502da2fbf78ad9d58a3476486a983dce7 100644 --- 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('<SNR>\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 index 1acc17adf4dbd5e95e8bd1bb724b58df92b1dbc8..4c1be4ecf101d3ce111fafb4cf99fa65b5525b58 100644 --- a/src/version.c +++ b/src/version.c @@ -755,6 +755,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 3339, /**/ 3338, /**/ diff --git a/src/vim9.h b/src/vim9.h index da39949fa34271ce7ae98caa49c88f1b63d94a5c..67c9a710a58b615198e8b8129da77ce197da18da 100644 --- 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 index d6a7e0aa377d398af8c7f0d9c99d5139d8a0fa33..e29d963778db54990d94a3f42f8c3a1f7e510442 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -2262,12 +2262,12 @@ generate_PUT(cctx_T *cctx, int regname, linenr_T lnum) } 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 *eap, cctx_T *cctx) 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 index 84de4fee5a3e891275d79e06c819cf4d254d3848..04d5e5f27cea3007ee8fa6b38ac756851d65eae5 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1396,6 +1396,27 @@ fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx) 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 *instr, int instr_count, ufunc_T *ufunc) 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;