# HG changeset patch # User Bram Moolenaar # Date 1645372803 -3600 # Node ID 324d394e314e360778f100fde4140b57811949c8 # Parent 3a242565b8a1ee2bfc4c02542805fc28eb66c5c1 patch 8.2.4425: map() function does not check function arguments Commit: https://github.com/vim/vim/commit/eddd4fc4f6d9d626374a73861c891f95ef999133 Author: Bram Moolenaar Date: Sun Feb 20 15:52:28 2022 +0000 patch 8.2.4425: map() function does not check function arguments Problem: map() function does not check function arguments at compile time. Solution: Give an error if the arguments of a map() function are wrong. diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -517,7 +517,7 @@ arg_filter_func(type_T *type, type_T *de } /* - * Check second argument of map(). + * Check second argument of map(), the function. */ static int arg_map_func(type_T *type, type_T *decl_type UNUSED, argcontext_T *context) @@ -530,34 +530,66 @@ arg_map_func(type_T *type, type_T *decl_ if (type->tt_type == VAR_FUNC) { - if (type->tt_member != &t_any && type->tt_member != &t_unknown) - { - type_T *expected = NULL; - - if (context->arg_types[0].type_curr->tt_type == VAR_LIST - || context->arg_types[0].type_curr->tt_type == VAR_DICT) + type_T *expected_ret = NULL; + type_T *(args[2]); + type_T t_func_exp = {VAR_FUNC, 2, 0, 0, NULL, args}; + + if (context->arg_types[0].type_curr->tt_type == VAR_LIST + || context->arg_types[0].type_curr->tt_type == VAR_DICT) + { + // Use the declared type if possible, so that an error is given if + // a declared list changes type, but not if a constant list changes + // type. + if (context->arg_types[0].type_decl->tt_type == VAR_LIST + || context->arg_types[0].type_decl->tt_type == VAR_DICT) + expected_ret = context->arg_types[0].type_decl->tt_member; + else + expected_ret = context->arg_types[0].type_curr->tt_member; + } + else if (context->arg_types[0].type_curr->tt_type == VAR_STRING) + expected_ret = &t_string; + else if (context->arg_types[0].type_curr->tt_type == VAR_BLOB) + expected_ret = &t_number; + + args[0] = NULL; + args[1] = &t_unknown; + if (type->tt_argcount != -1) + { + if (!(type->tt_argcount == 2 || (type->tt_argcount == 1 + && (type->tt_flags & TTFLAG_VARARGS)))) { - // Use the declared type, so that an error is given if a - // declared list changes type, but not if a constant list - // changes type. - if (context->arg_types[0].type_decl->tt_type == VAR_LIST - || context->arg_types[0].type_decl->tt_type == VAR_DICT) - expected = context->arg_types[0].type_decl->tt_member; - else - expected = context->arg_types[0].type_curr->tt_member; + emsg(_(e_invalid_number_of_arguments)); + return FAIL; + } + if (type->tt_flags & TTFLAG_VARARGS) + // check the argument types at runtime + t_func_exp.tt_argcount = -1; + else + { + if (context->arg_types[0].type_decl->tt_type == VAR_LIST) + args[0] = &t_number; + else if (context->arg_types[0].type_decl->tt_type == VAR_DICT) + args[0] = &t_string; + if (args[0] != NULL) + args[1] = expected_ret; } - else if (context->arg_types[0].type_curr->tt_type == VAR_STRING) - expected = &t_string; - else if (context->arg_types[0].type_curr->tt_type == VAR_BLOB) - expected = &t_number; - if (expected != NULL) - { - type_T t_func_exp = {VAR_FUNC, -1, 0, TTFLAG_STATIC, - NULL, NULL}; - - t_func_exp.tt_member = expected; - return check_arg_type(&t_func_exp, type, context); - } + } + + if ((type->tt_member != &t_any && type->tt_member != &t_unknown) + || args[0] != NULL) + { + where_T where = WHERE_INIT; + + t_func_exp.tt_member = expected_ret == NULL + || type->tt_member == &t_any + || type->tt_member == &t_unknown + ? &t_any : expected_ret; + if (args[0] == NULL) + args[0] = &t_unknown; + return check_arg_type(&t_func_exp, type, context); + + where.wt_index = 2; + return check_type(&t_func_exp, type, TRUE, where); } } else diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -2289,11 +2289,11 @@ def Test_map_function_arg() lines =<< trim END range(3)->map((a, b, c) => a + b + c) END - v9.CheckDefExecAndScriptFailure(lines, 'E1190: One argument too few') + v9.CheckDefAndScriptFailure(lines, ['E176:', 'E1190: One argument too few']) lines =<< trim END range(3)->map((a, b, c, d) => a + b + c + d) END - v9.CheckDefExecAndScriptFailure(lines, 'E1190: 2 arguments too few') + v9.CheckDefAndScriptFailure(lines, ['E176:', 'E1190: 2 arguments too few']) # declared list cannot change type lines =<< trim END @@ -2303,7 +2303,7 @@ def Test_map_function_arg() var ll: list = [1, 2, 3] echo map(ll, Map) END - v9.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()']) + v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?number): number but got func(number, number): string', 'E1012: Type mismatch; expected number but got string in map()']) # not declared list can change type echo [1, 2, 3]->map((..._) => 'x') @@ -2321,19 +2321,19 @@ def Test_map_item_type() var l: list = [0] echo map(l, (_, v) => []) END - v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(...): number but got func(any, any): list', 'E1012: Type mismatch; expected number but got list in map()'], 2) + v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?number): number but got func(any, any): list', 'E1012: Type mismatch; expected number but got list in map()'], 2) lines =<< trim END var l: list = range(2) echo map(l, (_, v) => []) END - v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(...): number but got func(any, any): list', 'E1012: Type mismatch; expected number but got list in map()'], 2) + v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?number): number but got func(any, any): list', 'E1012: Type mismatch; expected number but got list in map()'], 2) lines =<< trim END var d: dict = {key: 0} echo map(d, (_, v) => []) END - v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(...): number but got func(any, any): list', 'E1012: Type mismatch; expected number but got list in map()'], 2) + v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?string, ?number): number but got func(any, any): list', 'E1012: Type mismatch; expected number but got list in map()'], 2) enddef def Test_maparg() @@ -2359,6 +2359,22 @@ def Test_maparg() v9.CheckDefAndScriptFailure(['maparg("a", "b", 2)'], ['E1013: Argument 3: type mismatch, expected bool but got number', 'E1212: Bool required for argument 3']) v9.CheckDefAndScriptFailure(['maparg("a", "b", true, 2)'], ['E1013: Argument 4: type mismatch, expected bool but got number', 'E1212: Bool required for argument 4']) maparg('')->assert_equal('') + + var lines =<< trim END + var l = [123] + l->map((_, v: string) => 0) + END + v9.CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected func(?number, ?number): number but got func(any, string): number') + + lines =<< trim END + ['x']->map((i: string, v: string) => 'y') + END + v9.CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected func(?number, ?any): any but got func(string, string): string') + + lines =<< trim END + {a: 1}->map((i: number, v: number) => 0) + END + v9.CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected func(?string, ?any): any but got func(number, number): number') enddef def Test_maparg_mapset() diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -3732,12 +3732,12 @@ def Test_too_many_arguments() var lines =<< trim END echo [0, 1, 2]->map(() => 123) END - v9.CheckDefExecAndScriptFailure(lines, 'E1106: 2 arguments too many', 1) + v9.CheckDefAndScriptFailure(lines, ['E176:', 'E1106: 2 arguments too many'], 1) lines =<< trim END echo [0, 1, 2]->map((_) => 123) END - v9.CheckDefExecAndScriptFailure(lines, 'E1106: One argument too many', 1) + v9.CheckDefAndScriptFailure(lines, ['E176', 'E1106: One argument too many'], 1) enddef def Test_closing_brace_at_start_of_line() diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 4425, +/**/ 4424, /**/ 4423,