]> granicus.if.org Git - vim/commitdiff
patch 8.2.3897: Vim9: second argument of map() and filter() not checked v8.2.3897
authorBram Moolenaar <Bram@vim.org>
Sat, 25 Dec 2021 21:43:28 +0000 (21:43 +0000)
committerBram Moolenaar <Bram@vim.org>
Sat, 25 Dec 2021 21:43:28 +0000 (21:43 +0000)
Problem:    Vim9: the second argument of map() and filter() is not checked at
            compile time.
Solution:   Add more specific type check for the second argument.

src/evalfunc.c
src/globals.h
src/list.c
src/testdir/test_vim9_builtin.vim
src/testdir/test_vim9_expr.vim
src/testdir/test_vim9_script.vim
src/version.c

index 9e0fd805d84d1ff048730c0bbee56614f1933c67..03d64599b63cd2cc5dbad1a6f97729c8325dbca6 100644 (file)
@@ -485,6 +485,52 @@ arg_list_or_dict_or_blob_or_string(type_T *type, argcontext_T *context)
     return FAIL;
 }
 
+/*
+ * Check second argument of filter(): func must return a bool.
+ */
+    static int
+arg_filter_func(type_T *type, argcontext_T *context)
+{
+    if (type->tt_type == VAR_FUNC
+           && !(type->tt_member->tt_type == VAR_BOOL
+               || type->tt_member->tt_type == VAR_NUMBER
+               || type->tt_member->tt_type == VAR_ANY))
+    {
+       arg_type_mismatch(&t_func_bool, type, context->arg_idx + 1);
+       return FAIL;
+    }
+    return OK;
+}
+
+/*
+ * Check second argument of map().
+ */
+    static int
+arg_map_func(type_T *type, argcontext_T *context)
+{
+    if (type->tt_type == VAR_FUNC
+           && type->tt_member != &t_any
+           && type->tt_member != &t_unknown)
+    {
+       type_T *expected = NULL;
+
+       if (context->arg_types[0]->tt_type == VAR_LIST
+               || context->arg_types[0]->tt_type == VAR_DICT)
+           expected = context->arg_types[0]->tt_member;
+       else if (context->arg_types[0]->tt_type == VAR_STRING
+               || context->arg_types[0]->tt_type == VAR_BLOB)
+           expected = &t_number;
+       if (expected != NULL)
+       {
+           type_T t_func_exp = {VAR_FUNC, -1, 0, TTFLAG_STATIC,
+                                                              expected, NULL};
+
+           return check_arg_type(&t_func_exp, type, context);
+       }
+    }
+    return OK;
+}
+
 /*
  * Check "type" is a list of 'any' or a blob or a string.
  */
@@ -859,7 +905,9 @@ static argcheck_T arg23_insert[] = {arg_list_or_blob, arg_item_of_prev, arg_numb
 static argcheck_T arg1_len[] = {arg_len1};
 static argcheck_T arg3_libcall[] = {arg_string, arg_string, arg_string_or_nr};
 static argcheck_T arg14_maparg[] = {arg_string, arg_string, arg_bool, arg_bool};
-static argcheck_T arg2_mapfilter[] = {arg_list_or_dict_or_blob_or_string, NULL};
+static argcheck_T arg2_filter[] = {arg_list_or_dict_or_blob_or_string, arg_filter_func};
+static argcheck_T arg2_map[] = {arg_list_or_dict_or_blob_or_string, arg_map_func};
+static argcheck_T arg2_mapnew[] = {arg_list_or_dict_or_blob_or_string, NULL};
 static argcheck_T arg25_matchadd[] = {arg_string, arg_string, arg_number, arg_number, arg_dict_any};
 static argcheck_T arg25_matchaddpos[] = {arg_string, arg_list_any, arg_number, arg_number, arg_dict_any};
 static argcheck_T arg119_printf[] = {arg_string_or_nr, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
@@ -1437,7 +1485,7 @@ static funcentry_T global_functions[] =
                        ret_number_bool,    f_filereadable},
     {"filewritable",   1, 1, FEARG_1,      arg1_string,
                        ret_number,         f_filewritable},
-    {"filter",         2, 2, FEARG_1,      arg2_mapfilter,
+    {"filter",         2, 2, FEARG_1,      arg2_filter,
                        ret_first_arg,      f_filter},
     {"finddir",                1, 3, FEARG_1,      arg3_string_string_number,
                        ret_finddir,        f_finddir},
@@ -1703,13 +1751,13 @@ static funcentry_T global_functions[] =
                NULL
 #endif
                        },
-    {"map",            2, 2, FEARG_1,      arg2_mapfilter,
+    {"map",            2, 2, FEARG_1,      arg2_map,
                        ret_first_cont,     f_map},
     {"maparg",         1, 4, FEARG_1,      arg14_maparg,
                        ret_maparg,         f_maparg},
     {"mapcheck",       1, 3, FEARG_1,      arg3_string_string_bool,
                        ret_string,         f_mapcheck},
-    {"mapnew",         2, 2, FEARG_1,      arg2_mapfilter,
+    {"mapnew",         2, 2, FEARG_1,      arg2_mapnew,
                        ret_first_cont,     f_mapnew},
     {"mapset",         3, 3, FEARG_1,      arg3_string_bool_dict,
                        ret_void,           f_mapset},
index 45561dee3bba8f8abf639c64ff630b2f87053263..038d680e2f037733e654e5b58af36c04ed26ee52 100644 (file)
@@ -432,6 +432,7 @@ EXTERN type_T t_func_void INIT6(VAR_FUNC, -1, 0, TTFLAG_STATIC, &t_void, NULL);
 EXTERN type_T t_func_any INIT6(VAR_FUNC, -1, 0, TTFLAG_STATIC, &t_any, NULL);
 EXTERN type_T t_func_number INIT6(VAR_FUNC, -1, 0, TTFLAG_STATIC, &t_number, NULL);
 EXTERN type_T t_func_string INIT6(VAR_FUNC, -1, 0, TTFLAG_STATIC, &t_string, NULL);
+EXTERN type_T t_func_bool INIT6(VAR_FUNC, -1, 0, TTFLAG_STATIC, &t_bool, NULL);
 EXTERN type_T t_func_0_void INIT6(VAR_FUNC, 0, 0, TTFLAG_STATIC, &t_void, NULL);
 EXTERN type_T t_func_0_any INIT6(VAR_FUNC, 0, 0, TTFLAG_STATIC, &t_any, NULL);
 EXTERN type_T t_func_0_number INIT6(VAR_FUNC, 0, 0, TTFLAG_STATIC, &t_number, NULL);
index 61a26a35ddc2ebc436449730eda2a6c0bce4b13c..6fafef6371faf37182d6ebf79f01d81c9de26a58 100644 (file)
@@ -2280,7 +2280,7 @@ filter_map_one(
 
        // filter(): when expr is zero remove the item
        if (in_vim9script())
-           *remp = !tv2bool(newtv);
+           *remp = !tv_get_bool_chk(newtv, &error);
        else
            *remp = (tv_get_number_chk(newtv, &error) == 0);
        clear_tv(newtv);
index 547a2132a40490d2dc06277c58505889b0251b8e..43b9d3373bd6d487aa7b482b4574d4808a55226e 100644 (file)
@@ -1241,6 +1241,15 @@ enddef
 
 def Test_filter()
   CheckDefAndScriptFailure(['filter(1.1, "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got float', 'E1251: List, Dictionary, Blob or String required for argument 1'])
+
+  var lines =<< trim END
+    def F(i: number, v: any): string
+      return 'bad'
+    enddef
+    echo filter([1, 2, 3], F)
+  END
+  CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(...): bool', 'E1135: Using a String as a Bool:'])
+
   assert_equal([], filter([1, 2, 3], '0'))
   assert_equal([1, 2, 3], filter([1, 2, 3], '1'))
   assert_equal({b: 20}, filter({a: 10, b: 20}, 'v:val == 20'))
@@ -2141,6 +2150,14 @@ def Test_map_function_arg()
       range(3)->map((a, b, c, d) => a + b + c + d)
   END
   CheckDefExecAndScriptFailure(lines, 'E1190: 2 arguments too few')
+
+  lines =<< trim END
+    def Map(i: number, v: number): string
+      return 'bad'
+    enddef
+    echo map([1, 2, 3], Map)
+  END
+  CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(...): number but got func(number, number): string', 'E1012: Type mismatch; expected number but got string in map()'])
 enddef
 
 def Test_map_item_type()
@@ -2155,19 +2172,19 @@ def Test_map_item_type()
     var l: list<number> = [0]
     echo map(l, (_, v) => [])
   END
-  CheckDefExecAndScriptFailure(lines, 'E1012: Type mismatch; expected number but got list<unknown> in map()', 2)
+  CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(...): number but got func(any, any): list<unknown>', 'E1012: Type mismatch; expected number but got list<unknown> in map()'], 2)
 
   lines =<< trim END
     var l: list<number> = range(2)
     echo map(l, (_, v) => [])
   END
-  CheckDefExecAndScriptFailure(lines, 'E1012: Type mismatch; expected number but got list<unknown> in map()', 2)
+  CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(...): number but got func(any, any): list<unknown>', 'E1012: Type mismatch; expected number but got list<unknown> in map()'], 2)
 
   lines =<< trim END
     var d: dict<number> = {key: 0}
     echo map(d, (_, v) => [])
   END
-  CheckDefExecAndScriptFailure(lines, 'E1012: Type mismatch; expected number but got list<unknown> in map()', 2)
+  CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(...): number but got func(any, any): list<unknown>', 'E1012: Type mismatch; expected number but got list<unknown> in map()'], 2)
 enddef
 
 def Test_maparg()
index 9cf035a6189d1e78dd7c55b11ace6cb1223a8408..01b45385084e5e130c07f67895e7b8fbe561193f 100644 (file)
@@ -2077,7 +2077,7 @@ def Test_expr7_lambda()
             )
       assert_equal([111, 222, 111], ll)
 
-      var dl = [{key: 0}, {key: 22}]->filter(( _, v) => v['key'] )
+      var dl = [{key: 0}, {key: 22}]->filter(( _, v) => !!v['key'] )
       assert_equal([{key: 22}], dl)
 
       dl = [{key: 12}, {['foo']: 34}]
@@ -2236,7 +2236,7 @@ def Test_expr7_new_lambda()
             )
       assert_equal([111, 222, 111], ll)
 
-      var dl = [{key: 0}, {key: 22}]->filter(( _, v) => v['key'] )
+      var dl = [{key: 0}, {key: 22}]->filter(( _, v) => !!v['key'] )
       assert_equal([{key: 22}], dl)
 
       dl = [{key: 12}, {['foo']: 34}]
@@ -2308,7 +2308,7 @@ def Test_expr7_lambda_vim9script()
   lines =<< trim END
       search('"', 'cW', 0, 0, () =>
        synstack('.', col('.'))
-          ->map((_, v) => synIDattr(v, 'name'))->len())
+          ->mapnew((_, v) => synIDattr(v, 'name'))->len())
   END
   CheckDefAndScriptSuccess(lines)
 enddef
index dcfe8886462f7965ede22fad08d7376b690555c6..8975e1bf38d34f341ad969f38c669647a26d6428 100644 (file)
@@ -2048,7 +2048,7 @@ def Test_vim9script_funcref_other_script()
       return idx % 2 == 1
     enddef
     export def FastFilter(): list<number>
-      return range(10)->filter('FilterFunc')
+      return range(10)->filter('FilterFunc(v:key, v:val)')
     enddef
     export def FastFilterDirect(): list<number>
       return range(10)->filter(FilterFunc)
index a09a0bffea6978530290479a9fa9b55d9da403f5..adb3722db60bcde959764d322dde9b133db81a09 100644 (file)
@@ -749,6 +749,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3897,
 /**/
     3896,
 /**/