To: vim_dev@googlegroups.com Subject: Patch 8.2.1329 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.1329 Problem: Vim9: cannot define global function inside :def function. Solution: Assign to global variable instead of local. (closes #6584) Files: src/vim9compile.c, src/userfunc.c, src/proto/userfunc.pro, src/vim9.h, src/vim9execute.c, src/structs.h, src/misc2.c, src/proto/misc2.pro, src/testdir/test_vim9_func.vim, src/testdir/test_vim9_disassemble.vim *** ../vim-8.2.1328/src/vim9compile.c 2020-07-29 21:20:37.926626437 +0200 --- src/vim9compile.c 2020-07-31 21:25:34.810875456 +0200 *************** *** 1523,1528 **** --- 1523,1549 ---- } /* + * Generate an ISN_NEWFUNC instruction. + */ + static int + generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name) + { + isn_T *isn; + char_u *name; + + RETURN_OK_IF_SKIP(cctx); + name = vim_strsave(lambda_name); + if (name == NULL) + return FAIL; + if ((isn = generate_instr(cctx, ISN_NEWFUNC)) == NULL) + return FAIL; + isn->isn_arg.newfunc.nf_lambda = name; + isn->isn_arg.newfunc.nf_global = func_name; + + return OK; + } + + /* * Generate an ISN_JUMP instruction. */ static int *************** *** 4875,4885 **** static char_u * compile_nested_function(exarg_T *eap, cctx_T *cctx) { char_u *name_start = eap->arg; ! char_u *name_end = to_name_end(eap->arg, FALSE); char_u *name = get_lambda_name(); lvar_T *lvar; ufunc_T *ufunc; eap->arg = name_end; eap->getline = exarg_getline; --- 4896,4908 ---- static char_u * compile_nested_function(exarg_T *eap, cctx_T *cctx) { + int is_global = *eap->arg == 'g' && eap->arg[1] == ':'; char_u *name_start = eap->arg; ! char_u *name_end = to_name_end(eap->arg, is_global); char_u *name = get_lambda_name(); lvar_T *lvar; ufunc_T *ufunc; + int r; eap->arg = name_end; eap->getline = exarg_getline; *************** *** 4894,4909 **** && compile_def_function(ufunc, TRUE, cctx) == FAIL) return NULL; ! // Define a local variable for the function reference. ! lvar = reserve_local(cctx, name_start, name_end - name_start, ! TRUE, ufunc->uf_func_type); ! if (generate_FUNCREF(cctx, ufunc->uf_dfunc_idx) == FAIL ! || generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL) == FAIL) ! return NULL; // TODO: warning for trailing text? ! return (char_u *)""; } /* --- 4917,4944 ---- && compile_def_function(ufunc, TRUE, cctx) == FAIL) return NULL; ! if (is_global) ! { ! char_u *func_name = vim_strnsave(name_start + 2, ! name_end - name_start - 2); ! if (func_name == NULL) ! r = FAIL; ! else ! r = generate_NEWFUNC(cctx, name, func_name); ! } ! else ! { ! // Define a local variable for the function reference. ! lvar = reserve_local(cctx, name_start, name_end - name_start, ! TRUE, ufunc->uf_func_type); ! if (generate_FUNCREF(cctx, ufunc->uf_dfunc_idx) == FAIL) ! return NULL; ! r = generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL); ! } // TODO: warning for trailing text? ! return r == FAIL ? NULL : (char_u *)""; } /* *************** *** 7641,7646 **** --- 7676,7686 ---- } break; + case ISN_NEWFUNC: + vim_free(isn->isn_arg.newfunc.nf_lambda); + vim_free(isn->isn_arg.newfunc.nf_global); + break; + case ISN_2BOOL: case ISN_2STRING: case ISN_ADDBLOB: *** ../vim-8.2.1328/src/userfunc.c 2020-07-30 20:08:46.836890228 +0200 --- src/userfunc.c 2020-07-31 21:55:39.518688770 +0200 *************** *** 366,372 **** if (fp == NULL) return NULL; ! fp->uf_dfunc_idx = UF_NOT_COMPILED; fp->uf_refcount = 1; fp->uf_varargs = TRUE; fp->uf_flags = FC_CFUNC; --- 366,372 ---- if (fp == NULL) return NULL; ! fp->uf_def_status = UF_NOT_COMPILED; fp->uf_refcount = 1; fp->uf_varargs = TRUE; fp->uf_flags = FC_CFUNC; *************** *** 1069,1075 **** { // When there is a def-function index do not actually remove the // function, so we can find the index when defining the function again. ! if (fp->uf_def_status == UF_COMPILED) fp->uf_flags |= FC_DEAD; else hash_remove(&func_hashtab, hi); --- 1069,1076 ---- { // When there is a def-function index do not actually remove the // function, so we can find the index when defining the function again. ! // Do remove it when it's a copy. ! if (fp->uf_def_status == UF_COMPILED && (fp->uf_flags & FC_COPY) == 0) fp->uf_flags |= FC_DEAD; else hash_remove(&func_hashtab, hi); *************** *** 1122,1128 **** // clear this function func_clear_items(fp); funccal_unref(fp->uf_scoped, fp, force); ! clear_def_function(fp); } /* --- 1123,1130 ---- // clear this function func_clear_items(fp); funccal_unref(fp->uf_scoped, fp, force); ! if ((fp->uf_flags & FC_COPY) == 0) ! clear_def_function(fp); } /* *************** *** 1150,1161 **** func_clear_free(ufunc_T *fp, int force) { func_clear(fp, force); ! if (force || fp->uf_dfunc_idx == 0) func_free(fp, force); else fp->uf_flags |= FC_DEAD; } /* * Call a user function. --- 1152,1234 ---- func_clear_free(ufunc_T *fp, int force) { func_clear(fp, force); ! if (force || fp->uf_dfunc_idx == 0 || (fp->uf_flags & FC_COPY)) func_free(fp, force); else fp->uf_flags |= FC_DEAD; } + /* + * Copy already defined function "lambda" to a new function with name "global". + * This is for when a compiled function defines a global function. + */ + void + copy_func(char_u *lambda, char_u *global) + { + ufunc_T *ufunc = find_func_even_dead(lambda, TRUE, NULL); + ufunc_T *fp; + + if (ufunc == NULL) + semsg(_("E1102: lambda function not found: %s"), lambda); + else + { + // TODO: handle ! to overwrite + fp = find_func(global, TRUE, NULL); + if (fp != NULL) + { + semsg(_(e_funcexts), global); + return; + } + + fp = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(global) + 1); + if (fp == NULL) + return; + + fp->uf_varargs = ufunc->uf_varargs; + fp->uf_flags = (ufunc->uf_flags & ~FC_VIM9) | FC_COPY; + fp->uf_def_status = ufunc->uf_def_status; + fp->uf_dfunc_idx = ufunc->uf_dfunc_idx; + if (ga_copy_strings(&fp->uf_args, &ufunc->uf_args) == FAIL + || ga_copy_strings(&fp->uf_def_args, &ufunc->uf_def_args) + == FAIL + || ga_copy_strings(&fp->uf_lines, &ufunc->uf_lines) == FAIL) + goto failed; + + fp->uf_name_exp = ufunc->uf_name_exp == NULL ? NULL + : vim_strsave(ufunc->uf_name_exp); + if (ufunc->uf_arg_types != NULL) + { + fp->uf_arg_types = ALLOC_MULT(type_T *, fp->uf_args.ga_len); + if (fp->uf_arg_types == NULL) + goto failed; + mch_memmove(fp->uf_arg_types, ufunc->uf_arg_types, + sizeof(type_T *) * fp->uf_args.ga_len); + } + if (ufunc->uf_def_arg_idx != NULL) + { + fp->uf_def_arg_idx = ALLOC_MULT(int, fp->uf_def_args.ga_len + 1); + if (fp->uf_def_arg_idx == NULL) + goto failed; + mch_memmove(fp->uf_def_arg_idx, ufunc->uf_def_arg_idx, + sizeof(int) * fp->uf_def_args.ga_len + 1); + } + if (ufunc->uf_va_name != NULL) + { + fp->uf_va_name = vim_strsave(ufunc->uf_va_name); + if (fp->uf_va_name == NULL) + goto failed; + } + + fp->uf_refcount = 1; + STRCPY(fp->uf_name, global); + hash_add(&func_hashtab, UF2HIKEY(fp)); + } + return; + + failed: + func_clear_free(fp, TRUE); + } + /* * Call a user function. *************** *** 2521,2526 **** --- 2594,2601 ---- /* * ":function" also supporting nested ":def". + * When "name_arg" is not NULL this is a nested function, using "name_arg" for + * the function name. * Returns a pointer to the function or NULL if no function defined. */ ufunc_T * *** ../vim-8.2.1328/src/proto/userfunc.pro 2020-07-19 20:48:56.182995168 +0200 --- src/proto/userfunc.pro 2020-07-31 21:22:42.043661588 +0200 *************** *** 5,10 **** --- 5,11 ---- char_u *get_lambda_name(void); char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state); int get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg); + void copy_func(char_u *lambda, char_u *global); char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload); void emsg_funcname(char *ermsg, char_u *name); int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, evalarg_T *evalarg, funcexe_T *funcexe); *** ../vim-8.2.1328/src/vim9.h 2020-07-19 19:47:32.089046807 +0200 --- src/vim9.h 2020-07-31 19:47:45.113238862 +0200 *************** *** 79,84 **** --- 79,85 ---- ISN_PCALL_END, // cleanup after ISN_PCALL with cpf_top set ISN_RETURN, // return, result is on top of stack ISN_FUNCREF, // push a function ref to dfunc isn_arg.funcref + ISN_NEWFUNC, // create a global function from a lambda function // expression operations ISN_JUMP, // jump if condition is matched isn_arg.jump *************** *** 237,242 **** --- 238,249 ---- int fr_var_idx; // variable to store partial } funcref_T; + // arguments to ISN_NEWFUNC + typedef struct { + char_u *nf_lambda; // name of the lambda already defined + char_u *nf_global; // name of the global function to be created + } newfunc_T; + // arguments to ISN_CHECKLEN typedef struct { int cl_min_len; // minimum length *************** *** 281,286 **** --- 288,294 ---- script_T script; unlet_T unlet; funcref_T funcref; + newfunc_T newfunc; checklen_T checklen; shuffle_T shuffle; } isn_arg; *** ../vim-8.2.1328/src/vim9execute.c 2020-07-25 16:32:58.679704392 +0200 --- src/vim9execute.c 2020-07-31 21:50:58.167679107 +0200 *************** *** 723,729 **** --- 723,732 ---- dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx; if (dfunc->df_instr == NULL) + { + iemsg("using call_def_function() on not compiled function"); return FAIL; + } } CLEAR_FIELD(ectx); *************** *** 1726,1731 **** --- 1729,1743 ---- } break; + // Create a global function from a lambda. + case ISN_NEWFUNC: + { + newfunc_T *newfunc = &iptr->isn_arg.newfunc; + + copy_func(newfunc->nf_lambda, newfunc->nf_global); + } + break; + // jump if a condition is met case ISN_JUMP: { *************** *** 2912,2917 **** --- 2924,2938 ---- } break; + case ISN_NEWFUNC: + { + newfunc_T *newfunc = &iptr->isn_arg.newfunc; + + smsg("%4d NEWFUNC %s %s", current, + newfunc->nf_lambda, newfunc->nf_global); + } + break; + case ISN_JUMP: { char *when = "?"; *** ../vim-8.2.1328/src/structs.h 2020-07-18 21:40:22.796647296 +0200 --- src/structs.h 2020-07-31 21:37:45.391357646 +0200 *************** *** 1546,1551 **** --- 1546,1552 ---- /* * Structure to hold info for a user function. + * When adding a field check copy_func(). */ typedef struct { *************** *** 1618,1623 **** --- 1619,1625 ---- #define FC_NOARGS 0x200 // no a: variables in lambda #define FC_VIM9 0x400 // defined in vim9 script file #define FC_CFUNC 0x800 // defined as Lua C func + #define FC_COPY 0x1000 // copy of another function by copy_func() #define MAX_FUNC_ARGS 20 // maximum number of function arguments #define VAR_SHORT_LEN 20 // short variable name length *** ../vim-8.2.1328/src/misc2.c 2020-06-20 14:43:20.357653490 +0200 --- src/misc2.c 2020-07-31 21:18:57.376631551 +0200 *************** *** 2028,2033 **** --- 2028,2068 ---- } /* + * Copy a growing array that contains a list of strings. + */ + int + ga_copy_strings(garray_T *from, garray_T *to) + { + int i; + + ga_init2(to, sizeof(char_u *), 1); + if (ga_grow(to, from->ga_len) == FAIL) + return FAIL; + + for (i = 0; i < from->ga_len; ++i) + { + char_u *orig = ((char_u **)from->ga_data)[i]; + char_u *copy; + + if (orig == NULL) + copy = NULL; + else + { + copy = vim_strsave(orig); + if (copy == NULL) + { + to->ga_len = i; + ga_clear_strings(to); + return FAIL; + } + } + ((char_u **)to->ga_data)[i] = copy; + } + to->ga_len = from->ga_len; + return OK; + } + + /* * Initialize a growing array. Don't forget to set ga_itemsize and * ga_growsize! Or use ga_init2(). */ *** ../vim-8.2.1328/src/proto/misc2.pro 2020-06-20 14:43:20.357653490 +0200 --- src/proto/misc2.pro 2020-07-31 21:12:25.042337024 +0200 *************** *** 56,61 **** --- 56,62 ---- int vim_isspace(int x); void ga_clear(garray_T *gap); void ga_clear_strings(garray_T *gap); + int ga_copy_strings(garray_T *from, garray_T *to); void ga_init(garray_T *gap); void ga_init2(garray_T *gap, int itemsize, int growsize); int ga_grow(garray_T *gap, int n); *** ../vim-8.2.1328/src/testdir/test_vim9_func.vim 2020-07-29 20:00:35.123334575 +0200 --- src/testdir/test_vim9_func.vim 2020-07-31 21:57:44.802195409 +0200 *************** *** 133,138 **** --- 133,160 ---- CheckDefFailure(['func Nested()', 'endfunc'], 'E1086:') enddef + def Test_nested_global_function() + let lines =<< trim END + vim9script + def Outer() + def g:Inner(): string + return 'inner' + enddef + enddef + disass Outer + Outer() + assert_equal('inner', g:Inner()) + delfunc g:Inner + Outer() + assert_equal('inner', g:Inner()) + delfunc g:Inner + Outer() + assert_equal('inner', g:Inner()) + delfunc g:Inner + END + CheckScriptSuccess(lines) + enddef + func Test_call_default_args_from_func() call assert_equal('string', MyDefaultArgs()) call assert_equal('one', MyDefaultArgs('one')) *** ../vim-8.2.1328/src/testdir/test_vim9_disassemble.vim 2020-07-19 19:47:32.093046796 +0200 --- src/testdir/test_vim9_disassemble.vim 2020-07-31 22:03:21.444771182 +0200 *************** *** 699,704 **** --- 699,722 ---- instr) enddef + def NestedOuter() + def g:Inner() + echomsg "inner" + enddef + enddef + + def Test_nested_func() + let instr = execute('disassemble NestedOuter') + assert_match('NestedOuter\_s*' .. + 'def g:Inner()\_s*' .. + 'echomsg "inner"\_s*' .. + 'enddef\_s*' .. + '\d NEWFUNC \d\+ Inner\_s*' .. + '\d PUSHNR 0\_s*' .. + '\d RETURN', + instr) + enddef + def AndOr(arg: any): string if arg == 1 && arg != 2 || arg == 4 return 'yes' *** ../vim-8.2.1328/src/version.c 2020-07-30 22:14:29.576329749 +0200 --- src/version.c 2020-07-31 19:08:59.005199747 +0200 *************** *** 756,757 **** --- 756,759 ---- { /* Add new patch number below this line */ + /**/ + 1329, /**/ -- From "know your smileys": [:-) Frankenstein's monster /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///