# HG changeset patch # User Bram Moolenaar # Date 1588792504 -7200 # Node ID bc2c9ea94ec14f1a3a6d4f5e8f5e0bb76aca479d # Parent cefbaab2e368637baae773c0ef4c1f3d8ee0e320 patch 8.2.0703: Vim9: closure cannot store value in outer context Commit: https://github.com/vim/vim/commit/b68b346e6db6d3f848e0a89905fcd7777b73c5d8 Author: Bram Moolenaar Date: Wed May 6 21:06:30 2020 +0200 patch 8.2.0703: Vim9: closure cannot store value in outer context Problem: Vim9: closure cannot store value in outer context. Solution: Make storing value in outer context work. Make :disassemble accept a function reference. diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -4337,9 +4337,11 @@ set_ref_in_item( partial_T *pt = tv->vval.v_partial; int i; - // A partial does not have a copyID, because it cannot contain itself. - if (pt != NULL) + if (pt != NULL && pt->pt_copyID != copyID) { + // Didn't see this partial yet. + pt->pt_copyID = copyID; + abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID); if (pt->pt_dict != NULL) diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1812,6 +1812,7 @@ struct partial_S typval_T *pt_argv; // arguments in allocated array dict_T *pt_dict; // dict for "self" + int pt_copyID; // funcstack may contain pointer to partial }; typedef struct AutoPatCmd_S AutoPatCmd; 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 @@ -291,6 +291,42 @@ def Test_disassemble_call() res) enddef +def s:CreateRefs() + let local = 'a' + def Append(arg: string) + local ..= arg + enddef + g:Append = Append + def Get(): string + return local + enddef + g:Get = Get +enddef + +def Test_disassemble_closure() + CreateRefs() + let res = execute('disass g:Append') + assert_match('\d.*' .. + 'local ..= arg.*' .. + '\d LOADOUTER $0.*' .. + '\d LOAD arg\[-1\].*' .. + '\d CONCAT.*' .. + '\d STOREOUTER $0.*' .. + '\d PUSHNR 0.*' .. + '\d RETURN.*', + res) + + res = execute('disass g:Get') + assert_match('\d.*' .. + 'return local.*' .. + '\d LOADOUTER $0.*' .. + '\d RETURN.*', + res) + + unlet g:Append + unlet g:Get +enddef + def EchoArg(arg: string): string return arg diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -738,6 +738,32 @@ def Test_closure_using_argument() unlet g:UseVararg enddef +def MakeGetAndAppendRefs() + let local = 'a' + + def Append(arg: string) + local ..= arg + enddef + g:Append = Append + + def Get(): string + return local + enddef + g:Get = Get +enddef + +def Test_closure_append_get() + MakeGetAndAppendRefs() + assert_equal('a', g:Get()) + g:Append('-b') + assert_equal('a-b', g:Get()) + g:Append('-c') + assert_equal('a-b-c', g:Get()) + + unlet g:Append + unlet g:Get +enddef + def Test_nested_closure() let local = 'text' def Closure(arg: string): string diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -747,6 +747,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 703, +/**/ 702, /**/ 701, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -40,8 +40,9 @@ typedef enum { ISN_STOREW, // pop into window-local variable isn_arg.string ISN_STORET, // pop into tab-local variable isn_arg.string ISN_STORES, // pop into script variable isn_arg.loadstore + ISN_STOREOUTER, // pop variable into outer scope isn_arg.number ISN_STORESCRIPT, // pop into script variable isn_arg.script - ISN_STOREOPT, // pop into option isn_arg.string + ISN_STOREOPT, // pop into option isn_arg.string ISN_STOREENV, // pop into environment variable isn_arg.string ISN_STOREREG, // pop into register isn_arg.number // ISN_STOREOTHER, // pop into other script variable isn_arg.other. diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -4496,7 +4496,11 @@ compile_assignment(char_u *arg, exarg_T generate_LOADV(cctx, name + 2, TRUE); break; case dest_local: - generate_LOAD(cctx, ISN_LOAD, lvar->lv_idx, NULL, type); + if (lvar->lv_from_outer) + generate_LOAD(cctx, ISN_LOADOUTER, lvar->lv_idx, + NULL, type); + else + generate_LOAD(cctx, ISN_LOAD, lvar->lv_idx, NULL, type); break; } } @@ -4713,8 +4717,8 @@ compile_assignment(char_u *arg, exarg_T // optimization: turn "var = 123" from ISN_PUSHNR + ISN_STORE // into ISN_STORENR - if (instr->ga_len == instr_count + 1 - && isn->isn_type == ISN_PUSHNR) + if (!lvar->lv_from_outer && instr->ga_len == instr_count + 1 + && isn->isn_type == ISN_PUSHNR) { varnumber_T val = isn->isn_arg.number; garray_T *stack = &cctx->ctx_type_stack; @@ -4725,6 +4729,8 @@ compile_assignment(char_u *arg, exarg_T if (stack->ga_len > 0) --stack->ga_len; } + else if (lvar->lv_from_outer) + generate_STORE(cctx, ISN_STOREOUTER, lvar->lv_idx, NULL); else generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL); } @@ -6686,6 +6692,7 @@ delete_instr(isn_T *isn) case ISN_PUSHSPEC: case ISN_RETURN: case ISN_STORE: + case ISN_STOREOUTER: case ISN_STOREV: case ISN_STORENR: case ISN_STOREREG: diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1070,6 +1070,14 @@ call_def_function( *tv = *STACK_TV_BOT(0); break; + // store variable or argument in outer scope + case ISN_STOREOUTER: + --ectx.ec_stack.ga_len; + tv = STACK_OUT_TV_VAR(iptr->isn_arg.number); + clear_tv(tv); + *tv = *STACK_TV_BOT(0); + break; + // store s: variable in old script case ISN_STORES: { @@ -2133,7 +2141,7 @@ ex_disassemble(exarg_T *eap) int is_global = FALSE; fname = trans_function_name(&arg, &is_global, FALSE, - TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL, NULL); + TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD, NULL, NULL); if (fname == NULL) { semsg(_(e_invarg2), eap->arg); @@ -2275,12 +2283,17 @@ ex_disassemble(exarg_T *eap) break; case ISN_STORE: + case ISN_STOREOUTER: + { + char *add = iptr->isn_type == ISN_STORE ? "" : "OUTER"; + if (iptr->isn_arg.number < 0) - smsg("%4d STORE arg[%lld]", current, + smsg("%4d STORE%s arg[%lld]", current, add, (long long)(iptr->isn_arg.number + STACK_FRAME_SIZE)); else - smsg("%4d STORE $%lld", current, + smsg("%4d STORE%s $%lld", current, add, (long long)(iptr->isn_arg.number)); + } break; case ISN_STOREV: smsg("%4d STOREV v:%s", current,