]> granicus.if.org Git - vim/commitdiff
patch 8.2.1956: Vim9: cannot specify argument types for lambda v8.2.1956
authorBram Moolenaar <Bram@vim.org>
Thu, 5 Nov 2020 17:45:46 +0000 (18:45 +0100)
committerBram Moolenaar <Bram@vim.org>
Thu, 5 Nov 2020 17:45:46 +0000 (18:45 +0100)
Problem:    Vim9: cannot specify argument types for lambda.
Solution:   Allow adding argument types.  Check arguments when calling a
            function reference.

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

index 1859e5e59edad81b595e1d168fdd652327bb90e0..dc771533038fe69651913de8a25148731152def7 100644 (file)
@@ -3266,7 +3266,7 @@ eval7(
      * Lambda: {arg, arg -> expr}
      * Dictionary: {'key': val, 'key': val}
      */
-    case '{':  ret = get_lambda_tv(arg, rettv, evalarg);
+    case '{':  ret = get_lambda_tv(arg, rettv, FALSE, evalarg);
                if (ret == NOTDONE)
                    ret = eval_dict(arg, rettv, evalarg, FALSE);
                break;
@@ -3554,7 +3554,7 @@ eval_lambda(
     *arg += 2;
     rettv->v_type = VAR_UNKNOWN;
 
-    ret = get_lambda_tv(arg, rettv, evalarg);
+    ret = get_lambda_tv(arg, rettv, FALSE, evalarg);
     if (ret != OK)
        return FAIL;
     else if (**arg != '(')
index a5b0367da1eda95261a88ed543150dc4ec2fecb1..5bc7a4b90fc065ca56028ff771d1c503fe42c2bc 100644 (file)
@@ -1,10 +1,10 @@
 /* userfunc.c */
 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);
+int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, garray_T *argtypes, int types_optional, int *varargs, garray_T *default_args, int skip, exarg_T *eap, char_u **line_to_free);
 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);
+int get_lambda_tv(char_u **arg, typval_T *rettv, int types_optional, evalarg_T *evalarg);
 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);
index 3747ad54d8f02620e73c3b99ce6cae79e99b0afd..66ac9544b3f576c91429f7d9d938af133d50b40f 100644 (file)
@@ -788,6 +788,28 @@ def Test_disassemble_lambda()
         instr)
 enddef
 
+def LambdaWithType(): number
+  var Ref = {a: number -> a + 10}
+  return Ref(g:value)
+enddef
+
+def Test_disassemble_lambda_with_type()
+  g:value = 5
+  assert_equal(15, LambdaWithType())
+  var instr = execute('disassemble LambdaWithType')
+  assert_match('LambdaWithType\_s*' ..
+        'var Ref = {a: number -> a + 10}\_s*' ..
+        '\d FUNCREF <lambda>\d\+\_s*' ..
+        '\d STORE $0\_s*' ..
+        'return Ref(g:value)\_s*' ..
+        '\d LOADG g:value\_s*' ..
+        '\d LOAD $0\_s*' ..
+        '\d CHECKTYPE number stack\[-2\]\_s*' ..
+        '\d PCALL (argc 1)\_s*' ..
+        '\d RETURN',
+        instr)
+enddef
+
 def NestedOuter()
   def g:Inner()
     echomsg "inner"
index 113e99e6668b2151b0c6a58420a9005e9ebd0242..a3f21491f2162d1f16d9f88df606e52cff28da61 100644 (file)
@@ -322,8 +322,6 @@ def Test_call_wrong_args()
   CheckDefFailure(['bufnr(xxx)'], 'E1001:')
   CheckScriptFailure(['def Func(Ref: func(s: string))'], 'E475:')
 
-  CheckDefFailure(['echo {i -> 0}()'], 'E119: Not enough arguments for function: {i -> 0}()')
-
   var lines =<< trim END
     vim9script
     def Func(s: string)
@@ -378,6 +376,17 @@ def Test_call_wrong_args()
   delete('Xscript')
 enddef
 
+def Test_call_lambda_args()
+  CheckDefFailure(['echo {i -> 0}()'],
+                  'E119: Not enough arguments for function: {i -> 0}()')
+
+  var lines =<< trim END
+      var Ref = {x: number, y: number -> x + y}
+      echo Ref(1, 'x')
+  END
+  CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected number but got string')
+enddef
+
 " Default arg and varargs
 def MyDefVarargs(one: string, two = 'foo', ...rest: list<string>): string
   var res = one .. ',' .. two
index 5e1f199079cc563a14c6b1d86feab5038cee4da0..6e780ea0118009a03db2d86e2b22b040b28c042f 100644 (file)
@@ -54,11 +54,17 @@ func_tbl_get(void)
 /*
  * Get one function argument.
  * If "argtypes" is not NULL also get the type: "arg: type".
+ * If "types_optional" is TRUE a missing type is OK, use "any".
  * Return a pointer to after the type.
  * When something is wrong return "arg".
  */
     static char_u *
-one_function_arg(char_u *arg, garray_T *newargs, garray_T *argtypes, int skip)
+one_function_arg(
+       char_u      *arg,
+       garray_T    *newargs,
+       garray_T    *argtypes,
+       int         types_optional,
+       int         skip)
 {
     char_u     *p = arg;
     char_u     *arg_copy = NULL;
@@ -105,7 +111,7 @@ one_function_arg(char_u *arg, garray_T *newargs, garray_T *argtypes, int skip)
     }
 
     // get any type from "arg: type"
-    if (argtypes != NULL && ga_grow(argtypes, 1) == OK)
+    if (argtypes != NULL && (skip || ga_grow(argtypes, 1) == OK))
     {
        char_u *type = NULL;
 
@@ -118,22 +124,29 @@ one_function_arg(char_u *arg, garray_T *newargs, garray_T *argtypes, int skip)
        if (*p == ':')
        {
            ++p;
-           if (!VIM_ISWHITE(*p))
+           if (!skip && !VIM_ISWHITE(*p))
            {
                semsg(_(e_white_space_required_after_str), ":");
                return arg;
            }
            type = skipwhite(p);
            p = skip_type(type, TRUE);
-           type = vim_strnsave(type, p - type);
+           if (!skip)
+               type = vim_strnsave(type, p - type);
        }
-       else if (*skipwhite(p) != '=')
+       else if (*skipwhite(p) != '=' && !types_optional)
        {
            semsg(_(e_missing_argument_type_for_str),
                                            arg_copy == NULL ? arg : arg_copy);
            return arg;
        }
-       ((char_u **)argtypes->ga_data)[argtypes->ga_len++] = type;
+       if (!skip)
+       {
+           if (type == NULL && types_optional)
+               // lambda arguments default to "any" type
+               type = vim_strsave((char_u *)"any");
+           ((char_u **)argtypes->ga_data)[argtypes->ga_len++] = type;
+       }
     }
 
     return p;
@@ -148,6 +161,7 @@ get_function_args(
     char_u     endchar,
     garray_T   *newargs,
     garray_T   *argtypes,      // NULL unless using :def
+    int                types_optional, // types optional if "argtypes" is not NULL
     int                *varargs,
     garray_T   *default_args,
     int                skip,
@@ -196,7 +210,7 @@ get_function_args(
        {
            if (!skip)
                semsg(_(e_invarg2), *argp);
-           break;
+           goto err_ret;
        }
        if (*p == endchar)
            break;
@@ -213,12 +227,14 @@ get_function_args(
                // ...name: list<type>
                if (!eval_isnamec1(*p))
                {
-                   emsg(_(e_missing_name_after_dots));
-                   break;
+                   if (!skip)
+                       emsg(_(e_missing_name_after_dots));
+                   goto err_ret;
                }
 
                arg = p;
-               p = one_function_arg(p, newargs, argtypes, skip);
+               p = one_function_arg(p, newargs, argtypes, types_optional,
+                                                                        skip);
                if (p == arg)
                    break;
            }
@@ -226,7 +242,7 @@ get_function_args(
        else
        {
            arg = p;
-           p = one_function_arg(p, newargs, argtypes, skip);
+           p = one_function_arg(p, newargs, argtypes, types_optional, skip);
            if (p == arg)
                break;
 
@@ -303,6 +319,63 @@ err_ret:
     return FAIL;
 }
 
+/*
+ * Parse the argument types, filling "fp->uf_arg_types".
+ * Return OK or FAIL.
+ */
+    static int
+parse_argument_types(ufunc_T *fp, garray_T *argtypes, int varargs)
+{
+    ga_init2(&fp->uf_type_list, sizeof(type_T *), 10);
+    if (argtypes->ga_len > 0)
+    {
+       // When "varargs" is set the last name/type goes into uf_va_name
+       // and uf_va_type.
+       int len = argtypes->ga_len - (varargs ? 1 : 0);
+
+       if (len > 0)
+           fp->uf_arg_types = ALLOC_CLEAR_MULT(type_T *, len);
+       if (fp->uf_arg_types != NULL)
+       {
+           int i;
+           type_T      *type;
+
+           for (i = 0; i < len; ++ i)
+           {
+               char_u *p = ((char_u **)argtypes->ga_data)[i];
+
+               if (p == NULL)
+                   // will get the type from the default value
+                   type = &t_unknown;
+               else
+                   type = parse_type(&p, &fp->uf_type_list);
+               if (type == NULL)
+                   return FAIL;
+               fp->uf_arg_types[i] = type;
+           }
+       }
+       if (varargs)
+       {
+           char_u *p;
+
+           // Move the last argument "...name: type" to uf_va_name and
+           // uf_va_type.
+           fp->uf_va_name = ((char_u **)fp->uf_args.ga_data)
+                                                 [fp->uf_args.ga_len - 1];
+           --fp->uf_args.ga_len;
+           p = ((char_u **)argtypes->ga_data)[len];
+           if (p == NULL)
+               // todo: get type from default value
+               fp->uf_va_type = &t_any;
+           else
+               fp->uf_va_type = parse_type(&p, &fp->uf_type_list);
+           if (fp->uf_va_type == NULL)
+               return FAIL;
+       }
+    }
+    return OK;
+}
+
 /*
  * Register function "fp" as using "current_funccal" as its scope.
  */
@@ -386,16 +459,22 @@ register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
 
 /*
  * Parse a lambda expression and get a Funcref from "*arg".
+ * When "types_optional" is TRUE optionally take argument types.
  * Return OK or FAIL.  Returns NOTDONE for dict or {expr}.
  */
     int
-get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
+get_lambda_tv(
+       char_u      **arg,
+       typval_T    *rettv,
+       int         types_optional,
+       evalarg_T   *evalarg)
 {
     int                evaluate = evalarg != NULL
                                      && (evalarg->eval_flags & EVAL_EVALUATE);
     garray_T   newargs;
     garray_T   newlines;
     garray_T   *pnewargs;
+    garray_T   argtypes;
     ufunc_T    *fp = NULL;
     partial_T   *pt = NULL;
     int                varargs;
@@ -411,8 +490,9 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
 
     // First, check if this is a lambda expression. "->" must exist.
     s = skipwhite(*arg + 1);
-    ret = get_function_args(&s, '-', NULL, NULL, NULL, NULL, TRUE,
-                                                                  NULL, NULL);
+    ret = get_function_args(&s, '-', NULL,
+           types_optional ? &argtypes : NULL, types_optional,
+                                                NULL, NULL, TRUE, NULL, NULL);
     if (ret == FAIL || *s != '>')
        return NOTDONE;
 
@@ -422,9 +502,9 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
     else
        pnewargs = NULL;
     *arg = skipwhite(*arg + 1);
-    // TODO: argument types
-    ret = get_function_args(arg, '-', pnewargs, NULL, &varargs, NULL, FALSE,
-                                                                  NULL, NULL);
+    ret = get_function_args(arg, '-', pnewargs,
+           types_optional ? &argtypes : NULL, types_optional,
+                                           &varargs, NULL, FALSE, NULL, NULL);
     if (ret == FAIL || **arg != '>')
        goto errret;
 
@@ -489,6 +569,10 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
        hash_add(&func_hashtab, UF2HIKEY(fp));
        fp->uf_args = newargs;
        ga_init(&fp->uf_def_args);
+       if (types_optional
+                        && parse_argument_types(fp, &argtypes, FALSE) == FAIL)
+           goto errret;
+
        fp->uf_lines = newlines;
        if (current_funccal != NULL && eval_lavars)
        {
@@ -523,11 +607,15 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
        evalarg->eval_tofree = tofree;
     else
        vim_free(tofree);
+    if (types_optional)
+       ga_clear_strings(&argtypes);
     return OK;
 
 errret:
     ga_clear_strings(&newargs);
     ga_clear_strings(&newlines);
+    if (types_optional)
+       ga_clear_strings(&argtypes);
     vim_free(fp);
     vim_free(pt);
     if (evalarg != NULL && evalarg->eval_tofree == NULL)
@@ -2907,7 +2995,7 @@ define_function(exarg_T *eap, char_u *name_arg)
     // This may get more lines and make the pointers into the first line
     // invalid.
     if (get_function_args(&p, ')', &newargs,
-                       eap->cmdidx == CMD_def ? &argtypes : NULL,
+                       eap->cmdidx == CMD_def ? &argtypes : NULL, FALSE,
                         &varargs, &default_args, eap->skip,
                         eap, &line_to_free) == FAIL)
        goto errret_2;
@@ -3502,58 +3590,12 @@ define_function(exarg_T *eap, char_u *name_arg)
                cstack->cs_flags[i] |= CSF_FUNC_DEF;
        }
 
-       // parse the argument types
-       ga_init2(&fp->uf_type_list, sizeof(type_T *), 10);
-       if (argtypes.ga_len > 0)
+       if (parse_argument_types(fp, &argtypes, varargs) == FAIL)
        {
-           // When "varargs" is set the last name/type goes into uf_va_name
-           // and uf_va_type.
-           int len = argtypes.ga_len - (varargs ? 1 : 0);
-
-           if (len > 0)
-               fp->uf_arg_types = ALLOC_CLEAR_MULT(type_T *, len);
-           if (fp->uf_arg_types != NULL)
-           {
-               int     i;
-               type_T  *type;
-
-               for (i = 0; i < len; ++ i)
-               {
-                   p = ((char_u **)argtypes.ga_data)[i];
-                   if (p == NULL)
-                       // will get the type from the default value
-                       type = &t_unknown;
-                   else
-                       type = parse_type(&p, &fp->uf_type_list);
-                   if (type == NULL)
-                   {
-                       SOURCING_LNUM = lnum_save;
-                       goto errret_2;
-                   }
-                   fp->uf_arg_types[i] = type;
-               }
-           }
-           if (varargs)
-           {
-               // Move the last argument "...name: type" to uf_va_name and
-               // uf_va_type.
-               fp->uf_va_name = ((char_u **)fp->uf_args.ga_data)
-                                                     [fp->uf_args.ga_len - 1];
-               --fp->uf_args.ga_len;
-               p = ((char_u **)argtypes.ga_data)[len];
-               if (p == NULL)
-                   // todo: get type from default value
-                   fp->uf_va_type = &t_any;
-               else
-                   fp->uf_va_type = parse_type(&p, &fp->uf_type_list);
-               if (fp->uf_va_type == NULL)
-               {
-                   SOURCING_LNUM = lnum_save;
-                   goto errret_2;
-               }
-           }
-           varargs = FALSE;
+           SOURCING_LNUM = lnum_save;
+           goto errret_2;
        }
+       varargs = FALSE;
 
        // parse the return type, if any
        if (ret_type == NULL)
index d4cddb8fbc044409a05456e73d524f1b19700fb5..4c8579a63400de9e7aa7ffa39507106238466c50 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1956,
 /**/
     1955,
 /**/
index 4f1b62c7b3d5cc342123d0e1d9769ebebd3d62c5..5ade42ad5fdabd0c5eba9c7e5261c6dccad31a6f 100644 (file)
@@ -1695,6 +1695,30 @@ generate_PCALL(
                semsg(_(e_toomanyarg), name);
                return FAIL;
            }
+           if (type->tt_args != NULL)
+           {
+               int i;
+
+               for (i = 0; i < argcount; ++i)
+               {
+                   int     offset = -argcount + i - 1;
+                   type_T *actual = ((type_T **)stack->ga_data)[
+                                                      stack->ga_len + offset];
+                   type_T *expected;
+
+                   if (varargs && i >= type->tt_min_argcount - 1)
+                       expected = type->tt_args[
+                                        type->tt_min_argcount - 1]->tt_member;
+                   else
+                       expected = type->tt_args[i];
+                   if (need_type(actual, expected, offset,
+                                                   cctx, TRUE, FALSE) == FAIL)
+                   {
+                       arg_type_mismatch(expected, actual, i + 1);
+                       return FAIL;
+                   }
+               }
+           }
        }
        ret_type = type->tt_member;
     }
@@ -2835,7 +2859,7 @@ compile_lambda(char_u **arg, cctx_T *cctx)
     evalarg.eval_cctx = cctx;
 
     // Get the funcref in "rettv".
-    if (get_lambda_tv(arg, &rettv, &evalarg) != OK)
+    if (get_lambda_tv(arg, &rettv, TRUE, &evalarg) != OK)
     {
        clear_evalarg(&evalarg, NULL);
        return FAIL;
@@ -2844,7 +2868,6 @@ compile_lambda(char_u **arg, cctx_T *cctx)
     ufunc = rettv.vval.v_partial->pt_func;
     ++ufunc->uf_refcount;
     clear_tv(&rettv);
-    ga_init2(&ufunc->uf_type_list, sizeof(type_T *), 10);
 
     // The function will have one line: "return {expr}".
     // Compile it into instructions.
@@ -2880,7 +2903,7 @@ compile_lambda_call(char_u **arg, cctx_T *cctx)
     int                ret = FAIL;
 
     // Get the funcref in "rettv".
-    if (get_lambda_tv(arg, &rettv, &EVALARG_EVALUATE) == FAIL)
+    if (get_lambda_tv(arg, &rettv, TRUE, &EVALARG_EVALUATE) == FAIL)
        return FAIL;
 
     if (**arg != '(')
@@ -3796,10 +3819,12 @@ compile_expr7(
         */
        case '{':   {
                        char_u *start = skipwhite(*arg + 1);
+                       garray_T ga_arg;
 
                        // Find out what comes after the arguments.
                        ret = get_function_args(&start, '-', NULL,
-                                          NULL, NULL, NULL, TRUE, NULL, NULL);
+                                       &ga_arg, TRUE, NULL, NULL,
+                                                            TRUE, NULL, NULL);
                        if (ret != FAIL && *start == '>')
                            ret = compile_lambda(arg, cctx);
                        else