changeset 20295:bc2c9ea94ec1 v8.2.0703

patch 8.2.0703: Vim9: closure cannot store value in outer context Commit: https://github.com/vim/vim/commit/b68b346e6db6d3f848e0a89905fcd7777b73c5d8 Author: Bram Moolenaar <Bram@vim.org> 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.
author Bram Moolenaar <Bram@vim.org>
date Wed, 06 May 2020 21:15:04 +0200
parents cefbaab2e368
children f76a55f87992
files src/eval.c src/structs.h src/testdir/test_vim9_disassemble.vim src/testdir/test_vim9_func.vim src/version.c src/vim9.h src/vim9compile.c src/vim9execute.c
diffstat 8 files changed, 97 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- 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)
--- 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;
--- 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('<lambda>\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('<lambda>\d.*' ..
+        'return local.*' ..
+        '\d LOADOUTER $0.*' ..
+        '\d RETURN.*',
+        res)
+
+  unlet g:Append
+  unlet g:Get
+enddef
+
 
 def EchoArg(arg: string): string
   return arg
--- 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
--- 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,
--- 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.
--- 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:
--- 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,