]> granicus.if.org Git - vim/commitdiff
patch 8.2.0695: Vim9: cannot define a function inside a function v8.2.0695
authorBram Moolenaar <Bram@vim.org>
Mon, 4 May 2020 21:24:44 +0000 (23:24 +0200)
committerBram Moolenaar <Bram@vim.org>
Mon, 4 May 2020 21:24:44 +0000 (23:24 +0200)
Problem:    Vim9: cannot define a function inside a function.
Solution:   Initial support for :def inside :def.

src/proto/userfunc.pro
src/testdir/test_vim9_func.vim
src/userfunc.c
src/version.c
src/vim9compile.c
src/vim9execute.c

index 81d69386f08bbb81cd9e3cce8daa944da9c494d1..44ed397ea7e73aa5d9c2d2913f1edb04768f6d99 100644 (file)
@@ -2,6 +2,7 @@
 void func_init(void);
 hashtab_T *func_tbl_get(void);
 int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, garray_T *argtypes, int *varargs, garray_T *default_args, int skip, exarg_T *eap, char_u **line_to_free);
+char_u *get_lambda_name(void);
 int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate);
 char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload);
 void emsg_funcname(char *ermsg, char_u *name);
@@ -22,6 +23,7 @@ void user_func_error(int error, char_u *name);
 int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe);
 char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial);
 char_u *untrans_function_name(char_u *name);
+ufunc_T *def_function(exarg_T *eap, char_u *name_arg, void *context);
 void ex_function(exarg_T *eap);
 int eval_fname_script(char_u *p);
 int translated_function_exists(char_u *name, int is_global);
index c06cc2304b362140cbd85e007f0be2e7e19618e3..712733149892ef8503aa405f9c94b121789177d0 100644 (file)
@@ -2,19 +2,7 @@
 
 source check.vim
 source view_util.vim
-
-" Check that "lines" inside ":def" results in an "error" message.
-func CheckDefFailure(lines, error)
-  call writefile(['def Func()'] + a:lines + ['enddef'], 'Xdef')
-  call assert_fails('so Xdef', a:error, a:lines)
-  call delete('Xdef')
-endfunc
-
-func CheckScriptFailure(lines, error)
-  call writefile(a:lines, 'Xdef')
-  call assert_fails('so Xdef', a:error, a:lines)
-  call delete('Xdef')
-endfunc
+source vim9.vim
 
 func Test_def_basic()
   def SomeFunc(): string
@@ -95,8 +83,17 @@ def Test_call_default_args()
   assert_equal('one', MyDefaultArgs('one'))
   assert_fails('call MyDefaultArgs("one", "two")', 'E118:')
 
-  call CheckScriptFailure(['def Func(arg: number = asdf)', 'enddef'], 'E1001:')
-  call CheckScriptFailure(['def Func(arg: number = "text")', 'enddef'], 'E1013: argument 1: type mismatch, expected number but got string')
+  CheckScriptFailure(['def Func(arg: number = asdf)', 'enddef'], 'E1001:')
+  CheckScriptFailure(['def Func(arg: number = "text")', 'enddef'], 'E1013: argument 1: type mismatch, expected number but got string')
+enddef
+
+def Test_nested_function()
+  def Nested(arg: string): string
+    return 'nested ' .. arg
+  enddef
+  assert_equal('nested function', Nested('function'))
+
+  CheckDefFailure(['func Nested()', 'endfunc'], 'E1086:')
 enddef
 
 func Test_call_default_args_from_func()
@@ -721,5 +718,13 @@ def Test_closure_using_argument()
   unlet g:UseVararg
 enddef
 
+def Test_nested_closure()
+  let local = 'text'
+  def Closure(arg: string): string
+    return local .. arg
+  enddef
+  assert_equal('text!!!', Closure('!!!'))
+enddef
+
 
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
index 4dac28126f99ba3721b747fb7be154df4e137232..1d5f5d5bc1490b1d37d5340e15541d58e06cc4f4 100644 (file)
@@ -328,6 +328,19 @@ set_ufunc_name(ufunc_T *fp, char_u *name)
     }
 }
 
+/*
+ * Get a name for a lambda.  Returned in static memory.
+ */
+    char_u *
+get_lambda_name(void)
+{
+    static char_u   name[30];
+    static int     lambda_no = 0;
+
+    sprintf((char*)name, "<lambda>%d", ++lambda_no);
+    return name;
+}
+
 /*
  * Parse a lambda expression and get a Funcref from "*arg".
  * Return OK or FAIL.  Returns NOTDONE for dict or {expr}.
@@ -344,7 +357,6 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
     int                ret;
     char_u     *start = skipwhite(*arg + 1);
     char_u     *s, *e;
-    static int lambda_no = 0;
     int                *old_eval_lavars = eval_lavars_used;
     int                eval_lavars = FALSE;
 
@@ -392,9 +404,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
     {
        int         len, flags = 0;
        char_u      *p;
-       char_u      name[20];
-
-       sprintf((char*)name, "<lambda>%d", ++lambda_no);
+       char_u      *name = get_lambda_name();
 
        fp = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
        if (fp == NULL)
@@ -2364,10 +2374,11 @@ untrans_function_name(char_u *name)
 }
 
 /*
- * ":function"
+ * ":function" also supporting nested ":def".
+ * Returns a pointer to the function or NULL if no function defined.
  */
-    void
-ex_function(exarg_T *eap)
+    ufunc_T *
+def_function(exarg_T *eap, char_u *name_arg, void *context)
 {
     char_u     *theline;
     char_u     *line_to_free = NULL;
@@ -2375,7 +2386,7 @@ ex_function(exarg_T *eap)
     int                c;
     int                saved_did_emsg;
     int                saved_wait_return = need_wait_return;
-    char_u     *name = NULL;
+    char_u     *name = name_arg;
     int                is_global = FALSE;
     char_u     *p;
     char_u     *arg;
@@ -2387,7 +2398,7 @@ ex_function(exarg_T *eap)
     int                varargs = FALSE;
     int                flags = 0;
     char_u     *ret_type = NULL;
-    ufunc_T    *fp;
+    ufunc_T    *fp = NULL;
     int                overwrite = FALSE;
     int                indent;
     int                nesting;
@@ -2429,7 +2440,7 @@ ex_function(exarg_T *eap)
            }
        }
        eap->nextcmd = check_nextcmd(eap->arg);
-       return;
+       return NULL;
     }
 
     /*
@@ -2469,7 +2480,7 @@ ex_function(exarg_T *eap)
        if (*p == '/')
            ++p;
        eap->nextcmd = check_nextcmd(p);
-       return;
+       return NULL;
     }
 
     ga_init(&newargs);
@@ -2493,25 +2504,34 @@ ex_function(exarg_T *eap)
      * g:func      global function name, same as "func"
      */
     p = eap->arg;
-    name = trans_function_name(&p, &is_global, eap->skip,
-                                                TFN_NO_AUTOLOAD, &fudi, NULL);
-    paren = (vim_strchr(p, '(') != NULL);
-    if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip)
+    if (name_arg != NULL)
     {
-       /*
-        * Return on an invalid expression in braces, unless the expression
-        * evaluation has been cancelled due to an aborting error, an
-        * interrupt, or an exception.
-        */
-       if (!aborting())
+       // nested function, argument is (args).
+       paren = TRUE;
+       CLEAR_FIELD(fudi);
+    }
+    else
+    {
+       name = trans_function_name(&p, &is_global, eap->skip,
+                                                TFN_NO_AUTOLOAD, &fudi, NULL);
+       paren = (vim_strchr(p, '(') != NULL);
+       if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip)
        {
-           if (!eap->skip && fudi.fd_newkey != NULL)
-               semsg(_(e_dictkey), fudi.fd_newkey);
-           vim_free(fudi.fd_newkey);
-           return;
+           /*
+            * Return on an invalid expression in braces, unless the expression
+            * evaluation has been cancelled due to an aborting error, an
+            * interrupt, or an exception.
+            */
+           if (!aborting())
+           {
+               if (!eap->skip && fudi.fd_newkey != NULL)
+                   semsg(_(e_dictkey), fudi.fd_newkey);
+               vim_free(fudi.fd_newkey);
+               return NULL;
+           }
+           else
+               eap->skip = TRUE;
        }
-       else
-           eap->skip = TRUE;
     }
 
     // An error in a function call during evaluation of an expression in magic
@@ -2596,7 +2616,7 @@ ex_function(exarg_T *eap)
 
     ga_init2(&newlines, (int)sizeof(char_u *), 3);
 
-    if (!eap->skip)
+    if (!eap->skip && name_arg == NULL)
     {
        // Check the name of the function.  Unless it's a dictionary function
        // (that we are overwriting).
@@ -3255,7 +3275,7 @@ ex_function(exarg_T *eap)
 
     // ":def Func()" needs to be compiled
     if (eap->cmdidx == CMD_def)
-       compile_def_function(fp, FALSE, NULL);
+       compile_def_function(fp, FALSE, context);
 
     goto ret_free;
 
@@ -3269,10 +3289,22 @@ ret_free:
     vim_free(skip_until);
     vim_free(line_to_free);
     vim_free(fudi.fd_newkey);
-    vim_free(name);
+    if (name != name_arg)
+       vim_free(name);
     vim_free(ret_type);
     did_emsg |= saved_did_emsg;
     need_wait_return |= saved_wait_return;
+
+    return fp;
+}
+
+/*
+ * ":function"
+ */
+    void
+ex_function(exarg_T *eap)
+{
+    def_function(eap, NULL, NULL);
 }
 
 /*
index 982170eb8fad7173cd3fcb9ac8b74751dbd0c8a1..da1a20f41ffa3ef91df3c4920052d713fc34ba3c 100644 (file)
@@ -746,6 +746,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    695,
 /**/
     694,
 /**/
index 300adfbec5adc593b87b858a4d1a78c887a471b5..7e700d5637a444eb239740d42d6ffb067d74338d 100644 (file)
@@ -101,6 +101,7 @@ typedef struct {
     int                lv_from_outer;  // when TRUE using ctx_outer scope
     int                lv_const;       // when TRUE cannot be assigned to
     int                lv_arg;         // when TRUE this is an argument
+    int                lv_func_idx;    // for nested function
 } lvar_T;
 
 /*
@@ -2615,6 +2616,7 @@ compile_call(char_u **arg, size_t varlen, cctx_T *cctx, int argcount_init)
     int                error = FCERR_NONE;
     ufunc_T    *ufunc;
     int                res = FAIL;
+    lvar_T     *lvar;
 
     if (varlen >= sizeof(namebuf))
     {
@@ -2641,6 +2643,16 @@ compile_call(char_u **arg, size_t varlen, cctx_T *cctx, int argcount_init)
        goto theend;
     }
 
+    // Check if the name is a nested function.
+    lvar = lookup_local(namebuf, varlen, cctx);
+    if (lvar != NULL && lvar->lv_func_idx > 0)
+    {
+       dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
+                                                          + lvar->lv_func_idx;
+       res = generate_CALL(cctx, dfunc->df_ufunc, argcount);
+       goto theend;
+    }
+
     // If we can find the function by name generate the right call.
     ufunc = find_func(name, FALSE, cctx);
     if (ufunc != NULL)
@@ -4048,6 +4060,64 @@ compile_return(char_u *arg, int set_return_type, cctx_T *cctx)
     return skipwhite(p);
 }
 
+/*
+ * Get a line from the compilation context, compatible with exarg_T getline().
+ * Return a pointer to the line in allocated memory.
+ * Return NULL for end-of-file or some error.
+ */
+    static char_u *
+exarg_getline(
+       int c UNUSED,
+       void *cookie,
+       int indent UNUSED,
+       int do_concat UNUSED)
+{
+    cctx_T  *cctx = (cctx_T *)cookie;
+
+    if (cctx->ctx_lnum == cctx->ctx_ufunc->uf_lines.ga_len)
+    {
+       iemsg("Heredoc got to end");
+       return NULL;
+    }
+    ++cctx->ctx_lnum;
+    return vim_strsave(((char_u **)cctx->ctx_ufunc->uf_lines.ga_data)
+                                                            [cctx->ctx_lnum]);
+}
+
+/*
+ * Compile a nested :def command.
+ */
+    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;
+    eap->cookie = cctx;
+    eap->skip = cctx->ctx_skip == TRUE;
+    eap->forceit = FALSE;
+    ufunc = def_function(eap, name, cctx);
+
+    if (ufunc == NULL)
+       return NULL;
+
+    // Define a local variable for the function, but change the index to -1 to
+    // mark it as a function name.
+    lvar = reserve_local(cctx, name_start, name_end - name_start,
+                                                      TRUE, &t_func_unknown);
+    lvar->lv_idx = 0;
+    ++cctx->ctx_locals_count;  // doesn't count as a local variable
+    lvar->lv_func_idx = ufunc->uf_dfunc_idx;
+
+    // TODO: warning for trailing?
+    return (char_u *)"";
+}
+
 /*
  * Return the length of an assignment operator, or zero if there isn't one.
  */
@@ -4077,30 +4147,6 @@ static char *reserved[] = {
     NULL
 };
 
-/*
- * Get a line for "=<<".
- * Return a pointer to the line in allocated memory.
- * Return NULL for end-of-file or some error.
- */
-    static char_u *
-heredoc_getline(
-       int c UNUSED,
-       void *cookie,
-       int indent UNUSED,
-       int do_concat UNUSED)
-{
-    cctx_T  *cctx = (cctx_T *)cookie;
-
-    if (cctx->ctx_lnum == cctx->ctx_ufunc->uf_lines.ga_len)
-    {
-       iemsg("Heredoc got to end");
-       return NULL;
-    }
-    ++cctx->ctx_lnum;
-    return vim_strsave(((char_u **)cctx->ctx_ufunc->uf_lines.ga_data)
-                                                            [cctx->ctx_lnum]);
-}
-
 typedef enum {
     dest_local,
     dest_option,
@@ -4394,7 +4440,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
        listitem_T *li;
 
        // [let] varname =<< [trim] {end}
-       eap->getline = heredoc_getline;
+       eap->getline = exarg_getline;
        eap->cookie = cctx;
        l = heredoc_get(eap, op + 3, FALSE);
 
@@ -6299,9 +6345,12 @@ compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx)
        switch (ea.cmdidx)
        {
            case CMD_def:
+                   ea.arg = p;
+                   line = compile_nested_function(&ea, &cctx);
+                   break;
+
            case CMD_function:
-                   // TODO: Nested function
-                   emsg("Nested function not implemented yet");
+                   emsg(_("E1086: Cannot use :function inside :def"));
                    goto erret;
 
            case CMD_return:
index c74240ff2037f217b3b12da791959dcbea602b23..665c6afd29ec9890ddd4ec657c13d59d7697c6cc 100644 (file)
@@ -206,6 +206,11 @@ call_dfunc(int cdf_idx, int argcount_arg, ectx_T *ectx)
                       + dfunc->df_varcount + dfunc->df_closure_count) == FAIL)
        return FAIL;
 
+    // Closure may need the function context where it was defined.
+    // TODO: assuming current context.
+    ectx->ec_outer_stack = &ectx->ec_stack;
+    ectx->ec_outer_frame = ectx->ec_frame_idx;
+
     // Move the vararg-list to below the missing optional arguments.
     if (vararg_count > 0 && arg_to_add > 0)
        *STACK_TV_BOT(arg_to_add - 1) = *STACK_TV_BOT(-1);