0
0
mirror of https://github.com/vim/vim.git synced 2025-10-03 05:14:07 -04:00

patch 8.2.3897: Vim9: second argument of map() and filter() not checked

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.
This commit is contained in:
Bram Moolenaar
2021-12-25 21:43:28 +00:00
parent db8e5c21b9
commit 1802405d71
7 changed files with 80 additions and 12 deletions

View File

@@ -485,6 +485,52 @@ arg_list_or_dict_or_blob_or_string(type_T *type, argcontext_T *context)
return FAIL; 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. * 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 arg1_len[] = {arg_len1};
static argcheck_T arg3_libcall[] = {arg_string, arg_string, arg_string_or_nr}; 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 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_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 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}; 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}, ret_number_bool, f_filereadable},
{"filewritable", 1, 1, FEARG_1, arg1_string, {"filewritable", 1, 1, FEARG_1, arg1_string,
ret_number, f_filewritable}, ret_number, f_filewritable},
{"filter", 2, 2, FEARG_1, arg2_mapfilter, {"filter", 2, 2, FEARG_1, arg2_filter,
ret_first_arg, f_filter}, ret_first_arg, f_filter},
{"finddir", 1, 3, FEARG_1, arg3_string_string_number, {"finddir", 1, 3, FEARG_1, arg3_string_string_number,
ret_finddir, f_finddir}, ret_finddir, f_finddir},
@@ -1703,13 +1751,13 @@ static funcentry_T global_functions[] =
NULL NULL
#endif #endif
}, },
{"map", 2, 2, FEARG_1, arg2_mapfilter, {"map", 2, 2, FEARG_1, arg2_map,
ret_first_cont, f_map}, ret_first_cont, f_map},
{"maparg", 1, 4, FEARG_1, arg14_maparg, {"maparg", 1, 4, FEARG_1, arg14_maparg,
ret_maparg, f_maparg}, ret_maparg, f_maparg},
{"mapcheck", 1, 3, FEARG_1, arg3_string_string_bool, {"mapcheck", 1, 3, FEARG_1, arg3_string_string_bool,
ret_string, f_mapcheck}, ret_string, f_mapcheck},
{"mapnew", 2, 2, FEARG_1, arg2_mapfilter, {"mapnew", 2, 2, FEARG_1, arg2_mapnew,
ret_first_cont, f_mapnew}, ret_first_cont, f_mapnew},
{"mapset", 3, 3, FEARG_1, arg3_string_bool_dict, {"mapset", 3, 3, FEARG_1, arg3_string_bool_dict,
ret_void, f_mapset}, ret_void, f_mapset},

View 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_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_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_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_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_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); EXTERN type_T t_func_0_number INIT6(VAR_FUNC, 0, 0, TTFLAG_STATIC, &t_number, NULL);

View File

@@ -2280,7 +2280,7 @@ filter_map_one(
// filter(): when expr is zero remove the item // filter(): when expr is zero remove the item
if (in_vim9script()) if (in_vim9script())
*remp = !tv2bool(newtv); *remp = !tv_get_bool_chk(newtv, &error);
else else
*remp = (tv_get_number_chk(newtv, &error) == 0); *remp = (tv_get_number_chk(newtv, &error) == 0);
clear_tv(newtv); clear_tv(newtv);

View File

@@ -1241,6 +1241,15 @@ enddef
def Test_filter() 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']) 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([], filter([1, 2, 3], '0'))
assert_equal([1, 2, 3], filter([1, 2, 3], '1')) assert_equal([1, 2, 3], filter([1, 2, 3], '1'))
assert_equal({b: 20}, filter({a: 10, b: 20}, 'v:val == 20')) 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) range(3)->map((a, b, c, d) => a + b + c + d)
END END
CheckDefExecAndScriptFailure(lines, 'E1190: 2 arguments too few') 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 enddef
def Test_map_item_type() def Test_map_item_type()
@@ -2155,19 +2172,19 @@ def Test_map_item_type()
var l: list<number> = [0] var l: list<number> = [0]
echo map(l, (_, v) => []) echo map(l, (_, v) => [])
END 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 lines =<< trim END
var l: list<number> = range(2) var l: list<number> = range(2)
echo map(l, (_, v) => []) echo map(l, (_, v) => [])
END 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 lines =<< trim END
var d: dict<number> = {key: 0} var d: dict<number> = {key: 0}
echo map(d, (_, v) => []) echo map(d, (_, v) => [])
END 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 enddef
def Test_maparg() def Test_maparg()

View File

@@ -2077,7 +2077,7 @@ def Test_expr7_lambda()
) )
assert_equal([111, 222, 111], ll) 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) assert_equal([{key: 22}], dl)
dl = [{key: 12}, {['foo']: 34}] dl = [{key: 12}, {['foo']: 34}]
@@ -2236,7 +2236,7 @@ def Test_expr7_new_lambda()
) )
assert_equal([111, 222, 111], ll) 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) assert_equal([{key: 22}], dl)
dl = [{key: 12}, {['foo']: 34}] dl = [{key: 12}, {['foo']: 34}]
@@ -2308,7 +2308,7 @@ def Test_expr7_lambda_vim9script()
lines =<< trim END lines =<< trim END
search('"', 'cW', 0, 0, () => search('"', 'cW', 0, 0, () =>
synstack('.', col('.')) synstack('.', col('.'))
->map((_, v) => synIDattr(v, 'name'))->len()) ->mapnew((_, v) => synIDattr(v, 'name'))->len())
END END
CheckDefAndScriptSuccess(lines) CheckDefAndScriptSuccess(lines)
enddef enddef

View File

@@ -2048,7 +2048,7 @@ def Test_vim9script_funcref_other_script()
return idx % 2 == 1 return idx % 2 == 1
enddef enddef
export def FastFilter(): list<number> export def FastFilter(): list<number>
return range(10)->filter('FilterFunc') return range(10)->filter('FilterFunc(v:key, v:val)')
enddef enddef
export def FastFilterDirect(): list<number> export def FastFilterDirect(): list<number>
return range(10)->filter(FilterFunc) return range(10)->filter(FilterFunc)

View File

@@ -749,6 +749,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
3897,
/**/ /**/
3896, 3896,
/**/ /**/