From a915fa010330ee7212e06d3511acd363d04d2d28 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Wed, 23 Mar 2022 11:29:15 +0000 Subject: 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) --- src/proto/vim9instr.pro | 2 +- src/testdir/test_vim9_func.vim | 19 +++++++++++++++++++ src/version.c | 2 ++ src/vim9compile.c | 43 ++++++++++++++++++++++++------------------ src/vim9expr.c | 2 +- src/vim9instr.c | 5 ++++- 6 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro index 8da559745a..76f3b21029 100644 --- a/src/proto/vim9instr.pro +++ b/src/proto/vim9instr.pro @@ -38,7 +38,7 @@ int generate_OLDSCRIPT(cctx_T *cctx, isntype_T isn_type, char_u *name, int sid, 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); diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim index e884f0e3c4..06168e477e 100644 --- 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() diff --git a/src/version.c b/src/version.c index 48eb0c5b01..f304ef3e6f 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 4612, /**/ 4611, /**/ diff --git a/src/vim9compile.c b/src/vim9compile.c index fde818882e..1eb6ce22fc 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -818,6 +818,7 @@ compile_nested_function(exarg_T *eap, cctx_T *cctx, garray_T *lines_to_free) 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, cctx_T *cctx, garray_T *lines_to_free) } } + // 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, cctx_T *cctx, garray_T *lines_to_free) 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); diff --git a/src/vim9expr.c b/src/vim9expr.c index 3a329ccf43..8c7d0b0e95 100644 --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -1040,7 +1040,7 @@ compile_lambda(char_u **arg, cctx_T *cctx) // 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); diff --git a/src/vim9instr.c b/src/vim9instr.c index 7f23884f3c..f0206211d8 100644 --- 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 *ufunc) 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 -- cgit v1.2.3