changeset 28173:b0c885507de4 v8.2.4612

patch 8.2.4612: Vim9: cannot use a recursive call in a nested function Commit: https://github.com/vim/vim/commit/a915fa010330ee7212e06d3511acd363d04d2d28 Author: Bram Moolenaar <Bram@vim.org> Date: Wed Mar 23 11:29:15 2022 +0000 patch 8.2.4612: Vim9: cannot use a recursive call in a nested function Problem: Vim9: cannot use a recursive call in a nested function. (Sergey Vlasov) Solution: Define the funcref before compiling the function. (closes #9989)
author Bram Moolenaar <Bram@vim.org>
date Wed, 23 Mar 2022 12:30:04 +0100
parents f5cd730da5b6
children da9fe882032a
files src/proto/vim9instr.pro src/testdir/test_vim9_func.vim src/version.c src/vim9compile.c src/vim9expr.c src/vim9instr.c
diffstat 6 files changed, 52 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/src/proto/vim9instr.pro
+++ b/src/proto/vim9instr.pro
@@ -38,7 +38,7 @@ int generate_OLDSCRIPT(cctx_T *cctx, isn
 int generate_VIM9SCRIPT(cctx_T *cctx, isntype_T isn_type, int sid, int idx, type_T *type);
 int generate_NEWLIST(cctx_T *cctx, int count);
 int generate_NEWDICT(cctx_T *cctx, int count);
-int generate_FUNCREF(cctx_T *cctx, ufunc_T *ufunc);
+int generate_FUNCREF(cctx_T *cctx, ufunc_T *ufunc, isn_T **isnp);
 int generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name);
 int generate_DEF(cctx_T *cctx, char_u *name, size_t len);
 int generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where);
--- a/src/testdir/test_vim9_func.vim
+++ b/src/testdir/test_vim9_func.vim
@@ -876,6 +876,25 @@ def Test_nested_function()
   END
   v9.CheckScriptSuccess(lines)
 
+  # nested function with recursive call
+  lines =<< trim END
+      vim9script
+
+      def MyFunc(): number
+        def Fib(n: number): number
+          if n < 2
+            return 1
+          endif
+          return Fib(n - 2) + Fib(n - 1)
+        enddef
+
+        return Fib(5)
+      enddef
+
+      assert_equal(8, MyFunc())
+  END
+  v9.CheckScriptSuccess(lines)
+
   lines =<< trim END
       vim9script
       def Outer()
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    4612,
+/**/
     4611,
 /**/
     4610,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -818,6 +818,7 @@ compile_nested_function(exarg_T *eap, cc
     ufunc_T	*ufunc;
     int		r = FAIL;
     compiletype_T   compile_type;
+    isn_T	*funcref_isn = NULL;
 
     if (eap->forceit)
     {
@@ -913,6 +914,27 @@ compile_nested_function(exarg_T *eap, cc
 	}
     }
 
+    // Define the funcref before compiling, so that it is found by any
+    // recursive call.
+    if (is_global)
+    {
+	r = generate_NEWFUNC(cctx, lambda_name, func_name);
+	func_name = NULL;
+	lambda_name = NULL;
+    }
+    else
+    {
+	// Define a local variable for the function reference.
+	lvar_T	*lvar = reserve_local(cctx, func_name, name_end - name_start,
+						    TRUE, ufunc->uf_func_type);
+
+	if (lvar == NULL)
+	    goto theend;
+	if (generate_FUNCREF(cctx, ufunc, &funcref_isn) == FAIL)
+	    goto theend;
+	r = generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL);
+    }
+
     compile_type = get_compile_type(ufunc);
 #ifdef FEAT_PROFILE
     // If the outer function is profiled, also compile the nested function for
@@ -934,24 +956,9 @@ compile_nested_function(exarg_T *eap, cc
 	compile_def_function(ufunc, FALSE, CT_NONE, cctx);
 #endif
 
-    if (is_global)
-    {
-	r = generate_NEWFUNC(cctx, lambda_name, func_name);
-	func_name = NULL;
-	lambda_name = NULL;
-    }
-    else
-    {
-	// Define a local variable for the function reference.
-	lvar_T	*lvar = reserve_local(cctx, func_name, name_end - name_start,
-						    TRUE, ufunc->uf_func_type);
-
-	if (lvar == NULL)
-	    goto theend;
-	if (generate_FUNCREF(cctx, ufunc) == FAIL)
-	    goto theend;
-	r = generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL);
-    }
+    // If a FUNCREF instruction was generated, set the index after compiling.
+    if (funcref_isn != NULL && ufunc->uf_def_status == UF_COMPILED)
+	funcref_isn->isn_arg.funcref.fr_dfunc_idx = ufunc->uf_dfunc_idx;
 
 theend:
     vim_free(lambda_name);
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -1040,7 +1040,7 @@ compile_lambda(char_u **arg, cctx_T *cct
 	// The function reference count will be 1.  When the ISN_FUNCREF
 	// instruction is deleted the reference count is decremented and the
 	// function is freed.
-	return generate_FUNCREF(cctx, ufunc);
+	return generate_FUNCREF(cctx, ufunc, NULL);
     }
 
     func_ptr_unref(ufunc);
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -1172,9 +1172,10 @@ generate_NEWDICT(cctx_T *cctx, int count
 
 /*
  * Generate an ISN_FUNCREF instruction.
+ * "isnp" is set to the instruction, so that fr_dfunc_idx can be set later.
  */
     int
-generate_FUNCREF(cctx_T *cctx, ufunc_T *ufunc)
+generate_FUNCREF(cctx_T *cctx, ufunc_T *ufunc, isn_T **isnp)
 {
     isn_T	*isn;
     type_T	*type;
@@ -1182,6 +1183,8 @@ generate_FUNCREF(cctx_T *cctx, ufunc_T *
     RETURN_OK_IF_SKIP(cctx);
     if ((isn = generate_instr(cctx, ISN_FUNCREF)) == NULL)
 	return FAIL;
+    if (isnp != NULL)
+	*isnp = isn;
     if (ufunc->uf_def_status == UF_NOT_COMPILED)
 	isn->isn_arg.funcref.fr_func_name = vim_strsave(ufunc->uf_name);
     else