# HG changeset patch # User Bram Moolenaar # Date 1627137003 -7200 # Node ID 75031a22be39994eb18e20faf4face50df656238 # Parent 1052b30de96926e28a5866bdbd9f1d47e1c54d77 patch 8.2.3211: Vim9: argument types are not checked at compile time Commit: https://github.com/vim/vim/commit/7973de35ba6840b7e106e2e8a8912522e9a2a960 Author: Yegappan Lakshmanan Date: Sat Jul 24 16:16:15 2021 +0200 patch 8.2.3211: Vim9: argument types are not checked at compile time Problem: Vim9: argument types are not checked at compile time. Solution: Add several more type checks. Fix type check for matchaddpos(). (Yegappan Lakshmanan, closes #8619) diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -4890,9 +4890,15 @@ f_ch_info(typval_T *argvars, typval_T *r void f_ch_log(typval_T *argvars, typval_T *rettv UNUSED) { - char_u *msg = tv_get_string(&argvars[0]); + char_u *msg; channel_T *channel = NULL; + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_opt_chan_or_job_arg(argvars, 1) == FAIL)) + return; + + msg = tv_get_string(&argvars[0]); if (argvars[1].v_type != VAR_UNKNOWN) channel = get_channel_arg(&argvars[1], FALSE, FALSE, 0); diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -542,7 +542,8 @@ arg_dict_any_or_string(type_T *type, arg } /* - * Check "type" which is the third argument of extend(). + * Check "type" which is the third argument of extend() (number or string or + * any) */ static int arg_extend3(type_T *type, argcontext_T *context) @@ -557,7 +558,8 @@ arg_extend3(type_T *type, argcontext_T * } /* - * Check "type" which is the second argument of remove(). + * Check "type" which is the second argument of remove() (number or string or + * any) */ static int arg_remove2(type_T *type, argcontext_T *context) @@ -572,7 +574,8 @@ arg_remove2(type_T *type, argcontext_T * } /* - * Check "type" which is the first argument of repeat(). + * Check "type" which is the first argument of repeat() (string or number or + * list or any) */ static int arg_repeat1(type_T *type, argcontext_T *context) @@ -588,7 +591,8 @@ arg_repeat1(type_T *type, argcontext_T * } /* - * Check "type" which is the first argument of slice(). + * Check "type" which is the first argument of slice() (list or blob or string + * or any) */ static int arg_slice1(type_T *type, argcontext_T *context) @@ -604,7 +608,8 @@ arg_slice1(type_T *type, argcontext_T *c } /* - * Check "type" which is the first argument of count(). + * Check "type" which is the first argument of count() (string or list or dict + * or any) */ static int arg_count1(type_T *type, argcontext_T *context) @@ -620,7 +625,8 @@ arg_count1(type_T *type, argcontext_T *c } /* - * Check "type" which is the first argument of cursor(). + * Check "type" which is the first argument of cursor() (number or string or + * list or any) */ static int arg_cursor1(type_T *type, argcontext_T *context) @@ -666,6 +672,7 @@ static argcheck_T arg2_string_list_nr[] static argcheck_T arg2_string_bool[] = {arg_string, arg_bool}; static argcheck_T arg2_string_or_list_dict[] = {arg_string_or_list_any, arg_dict_any}; static argcheck_T arg2_string_string_or_number[] = {arg_string, arg_string_or_nr}; +static argcheck_T arg2_string_chan_or_job[] = {arg_string, arg_chan_or_job}; static argcheck_T arg2_list_number[] = {arg_list_number, arg_list_number}; static argcheck_T arg2_list_number_bool[] = {arg_list_number, arg_bool}; static argcheck_T arg2_list_any_string[] = {arg_list_any, arg_string}; @@ -690,11 +697,14 @@ static argcheck_T arg2_buffer_number[] = static argcheck_T arg2_buffer_bool[] = {arg_buffer, arg_bool}; static argcheck_T arg2_buffer_lnum[] = {arg_buffer, arg_lnum}; static argcheck_T arg2_buffer_list_any[] = {arg_buffer, arg_list_any}; +static argcheck_T arg2_buffer_any[] = {arg_buffer, NULL}; static argcheck_T arg3_string[] = {arg_string, arg_string, arg_string}; static argcheck_T arg3_number[] = {arg_number, arg_number, arg_number}; static argcheck_T arg3_number_number_dict[] = {arg_number, arg_number, arg_dict_any}; +static argcheck_T arg3_number_string_string[] = {arg_number, arg_string, arg_string}; static argcheck_T arg3_number_string_any[] = {arg_number, arg_string, NULL}; static argcheck_T arg3_number_string_buffer[] = {arg_number, arg_string, arg_buffer}; +static argcheck_T arg3_number_any_dict[] = {arg_number, NULL, arg_dict_any}; static argcheck_T arg3_string_nr_bool[] = {arg_string, arg_number, arg_bool}; static argcheck_T arg3_string_string_nr[] = {arg_string, arg_string, arg_number}; static argcheck_T arg3_string_string_dict[] = {arg_string, arg_string, arg_dict_any}; @@ -707,6 +717,7 @@ static argcheck_T arg3_lnum_number_bool[ static argcheck_T arg3_buffer_lnum_lnum[] = {arg_buffer, arg_lnum, arg_lnum}; static argcheck_T arg3_buffer_number_number[] = {arg_buffer, arg_number, arg_number}; static argcheck_T arg3_buffer_string_dict[] = {arg_buffer, arg_string, arg_dict_any}; +static argcheck_T arg3_buffer_string_any[] = {arg_buffer, arg_string, NULL}; static argcheck_T arg4_number_number_string_any[] = {arg_number, arg_number, arg_string, NULL}; static argcheck_T arg4_string_string_number_string[] = {arg_string, arg_string, arg_number, arg_string}; static argcheck_T arg5_number[] = {arg_number, arg_number, arg_number, arg_number, arg_number}; @@ -726,12 +737,13 @@ static argcheck_T arg23_insert[] = {arg_ static argcheck_T arg2_mapfilter[] = {arg_list_or_dict_or_blob, NULL}; static argcheck_T arg14_maparg[] = {arg_string, arg_string, arg_bool, arg_bool}; 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_number, 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 arg23_reduce[] = {arg_list_or_blob, NULL, NULL}; static argcheck_T arg24_remote_expr[] = {arg_string, arg_string, arg_string, arg_number}; static argcheck_T arg23_remove[] = {arg_list_or_dict_or_blob, arg_remove2, arg_number}; static argcheck_T arg2_repeat[] = {arg_repeat1, arg_number}; static argcheck_T arg15_search[] = {arg_string, arg_string, arg_number, arg_number, NULL}; +static argcheck_T arg37_searchpair[] = {arg_string, arg_string, arg_string, arg_string, NULL, arg_number, arg_number}; static argcheck_T arg3_setbufline[] = {arg_buffer, arg_lnum, arg_str_or_nr_or_list}; static argcheck_T arg2_setline[] = {arg_lnum, NULL}; static argcheck_T arg24_setloclist[] = {arg_number, arg_list_any, arg_string, arg_dict_any}; @@ -1146,7 +1158,7 @@ static funcentry_T global_functions[] = ret_job, JOB_FUNC(f_ch_getjob)}, {"ch_info", 1, 1, FEARG_1, arg1_chan_or_job, ret_dict_any, JOB_FUNC(f_ch_info)}, - {"ch_log", 1, 2, FEARG_1, NULL, + {"ch_log", 1, 2, FEARG_1, arg2_string_chan_or_job, ret_void, JOB_FUNC(f_ch_log)}, {"ch_logfile", 1, 2, FEARG_1, arg2_string, ret_void, JOB_FUNC(f_ch_logfile)}, @@ -1202,7 +1214,7 @@ static funcentry_T global_functions[] = ret_float, FLOAT_FUNC(f_cosh)}, {"count", 2, 4, FEARG_1, arg24_count, ret_number, f_count}, - {"cscope_connection",0,3, 0, NULL, + {"cscope_connection",0,3, 0, arg3_number_string_string, ret_number, f_cscope_connection}, {"cursor", 1, 3, FEARG_1, arg13_cursor, ret_number, f_cursor}, @@ -1310,7 +1322,7 @@ static funcentry_T global_functions[] = ret_list_dict_any, f_getbufinfo}, {"getbufline", 2, 3, FEARG_1, arg3_buffer_lnum_lnum, ret_list_string, f_getbufline}, - {"getbufvar", 2, 3, FEARG_1, NULL, + {"getbufvar", 2, 3, FEARG_1, arg3_buffer_string_any, ret_any, f_getbufvar}, {"getchangelist", 0, 1, FEARG_1, arg1_buffer, ret_list_any, f_getchangelist}, @@ -1650,9 +1662,9 @@ static funcentry_T global_functions[] = ret_string, f_printf}, {"prompt_getprompt", 1, 1, FEARG_1, arg1_buffer, ret_string, JOB_FUNC(f_prompt_getprompt)}, - {"prompt_setcallback", 2, 2, FEARG_1, NULL, + {"prompt_setcallback", 2, 2, FEARG_1, arg2_buffer_any, ret_void, JOB_FUNC(f_prompt_setcallback)}, - {"prompt_setinterrupt", 2, 2, FEARG_1, NULL, + {"prompt_setinterrupt", 2, 2, FEARG_1, arg2_buffer_any, ret_void, JOB_FUNC(f_prompt_setinterrupt)}, {"prompt_setprompt", 2, 2, FEARG_1, arg2_buffer_string, ret_void, JOB_FUNC(f_prompt_setprompt)}, @@ -1780,9 +1792,9 @@ static funcentry_T global_functions[] = ret_dict_any, f_searchcount}, {"searchdecl", 1, 3, FEARG_1, arg3_string_bool_bool, ret_number_bool, f_searchdecl}, - {"searchpair", 3, 7, 0, NULL, + {"searchpair", 3, 7, 0, arg37_searchpair, ret_number, f_searchpair}, - {"searchpairpos", 3, 7, 0, NULL, + {"searchpairpos", 3, 7, 0, arg37_searchpair, ret_list_number, f_searchpairpos}, {"searchpos", 1, 5, FEARG_1, arg15_search, ret_list_number, f_searchpos}, @@ -1792,7 +1804,7 @@ static funcentry_T global_functions[] = ret_string, f_serverlist}, {"setbufline", 3, 3, FEARG_3, arg3_setbufline, ret_number_bool, f_setbufline}, - {"setbufvar", 3, 3, FEARG_3, NULL, + {"setbufvar", 3, 3, FEARG_3, arg3_buffer_string_any, ret_void, f_setbufvar}, {"setcellwidths", 1, 1, FEARG_1, arg1_list_any, ret_void, f_setcellwidths}, @@ -1950,7 +1962,7 @@ static funcentry_T global_functions[] = ret_string, f_swapname}, {"synID", 3, 3, 0, arg3_lnum_number_bool, ret_number, f_synID}, - {"synIDattr", 2, 3, FEARG_1, NULL, + {"synIDattr", 2, 3, FEARG_1, arg3_number_string_string, ret_string, f_synIDattr}, {"synIDtrans", 1, 1, FEARG_1, arg1_number, ret_number, f_synIDtrans}, @@ -2102,7 +2114,7 @@ static funcentry_T global_functions[] = ret_list_dict_any, TIMER_FUNC(f_timer_info)}, {"timer_pause", 2, 2, FEARG_1, arg2_number_bool, ret_void, TIMER_FUNC(f_timer_pause)}, - {"timer_start", 2, 3, FEARG_1, NULL, + {"timer_start", 2, 3, FEARG_1, arg3_number_any_dict, ret_number, TIMER_FUNC(f_timer_start)}, {"timer_stop", 1, 1, FEARG_1, arg1_number, ret_void, TIMER_FUNC(f_timer_stop)}, @@ -8044,6 +8056,18 @@ searchpair_cmn(typval_T *argvars, pos_T long lnum_stop = 0; long time_limit = 0; + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_string_arg(argvars, 1) == FAIL + || check_for_string_arg(argvars, 2) == FAIL + || check_for_opt_string_arg(argvars, 3) == FAIL + || (argvars[3].v_type != VAR_UNKNOWN + && argvars[4].v_type != VAR_UNKNOWN + && (check_for_opt_number_arg(argvars, 5) == FAIL + || (argvars[5].v_type != VAR_UNKNOWN + && check_for_opt_number_arg(argvars, 6) == FAIL))))) + goto theend; + // Get the three pattern arguments: start, middle, end. Will result in an // error if not a valid argument. spat = tv_get_string_chk(&argvars[0]); @@ -9245,6 +9269,13 @@ f_synIDattr(typval_T *argvars UNUSED, ty char_u modebuf[NUMBUFLEN]; int modec; + if (in_vim9script() + && (check_for_number_arg(argvars, 0) == FAIL + || (check_for_string_arg(argvars, 1) == FAIL + || (argvars[1].v_type != VAR_UNKNOWN + && check_for_opt_string_arg(argvars, 2) == FAIL)))) + return; + id = (int)tv_get_number(&argvars[0]); what = tv_get_string(&argvars[1]); if (argvars[2].v_type != VAR_UNKNOWN) diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -4112,6 +4112,11 @@ f_getbufvar(typval_T *argvars, typval_T dictitem_T *v; int done = FALSE; + if (in_vim9script() + && (check_for_buffer_arg(argvars, 0) == FAIL + || check_for_string_arg(argvars, 1) == FAIL)) + return; + varname = tv_get_string_chk(&argvars[1]); buf = tv_get_buf_from_arg(&argvars[0]); @@ -4251,6 +4256,12 @@ f_setbufvar(typval_T *argvars, typval_T if (check_secure()) return; + + if (in_vim9script() + && (check_for_buffer_arg(argvars, 0) == FAIL + || check_for_string_arg(argvars, 1) == FAIL)) + return; + varname = tv_get_string_chk(&argvars[1]); buf = tv_get_buf_from_arg(&argvars[0]); varp = &argvars[2]; diff --git a/src/if_cscope.c b/src/if_cscope.c --- a/src/if_cscope.c +++ b/src/if_cscope.c @@ -2496,6 +2496,14 @@ f_cscope_connection(typval_T *argvars UN char_u *prepend = NULL; char_u buf[NUMBUFLEN]; + if (in_vim9script() + && (check_for_opt_number_arg(argvars, 0) == FAIL + || (argvars[0].v_type != VAR_UNKNOWN + && (check_for_opt_string_arg(argvars, 1) == FAIL + || (argvars[1].v_type != VAR_UNKNOWN + && check_for_opt_string_arg(argvars, 2) == FAIL))))) + return; + if (argvars[0].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_UNKNOWN) { diff --git a/src/job.c b/src/job.c --- a/src/job.c +++ b/src/job.c @@ -1658,6 +1658,10 @@ f_prompt_setcallback(typval_T *argvars, if (check_secure()) return; + + if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL) + return; + buf = tv_get_buf(&argvars[0], FALSE); if (buf == NULL) return; @@ -1681,6 +1685,10 @@ f_prompt_setinterrupt(typval_T *argvars, if (check_secure()) return; + + if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL) + return; + buf = tv_get_buf(&argvars[0], FALSE); if (buf == NULL) return; diff --git a/src/proto/typval.pro b/src/proto/typval.pro --- a/src/proto/typval.pro +++ b/src/proto/typval.pro @@ -21,6 +21,7 @@ int check_for_opt_list_arg(typval_T *arg int check_for_dict_arg(typval_T *args, int idx); int check_for_opt_dict_arg(typval_T *args, int idx); int check_for_chan_or_job_arg(typval_T *args, int idx); +int check_for_opt_chan_or_job_arg(typval_T *args, int idx); int check_for_job_arg(typval_T *args, int idx); int check_for_string_or_number_arg(typval_T *args, int idx); int check_for_buffer_arg(typval_T *args, int idx); 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 @@ -445,6 +445,15 @@ def Test_ch_info() endif enddef +def Test_ch_log() + if !has('channel') + CheckFeature channel + else + CheckDefAndScriptFailure2(['ch_log(true)'], 'E1013: Argument 1: type mismatch, expected string but got bool', 'E1174: String required for argument 1') + CheckDefAndScriptFailure2(['ch_log("a", 1)'], 'E1013: Argument 2: type mismatch, expected channel but got number', 'E1217: Channel or Job required for argument 2') + endif +enddef + def Test_ch_logfile() if !has('channel') CheckFeature channel @@ -654,6 +663,14 @@ def Test_count() count({a: 1.1, b: 2.2, c: 1.1}, 1.1)->assert_equal(2) enddef +def Test_cscope_connection() + CheckFeature cscope + assert_equal(0, cscope_connection()) + CheckDefAndScriptFailure2(['cscope_connection("a")'], 'E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1') + CheckDefAndScriptFailure2(['cscope_connection(1, 2)'], 'E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2') + CheckDefAndScriptFailure2(['cscope_connection(1, "b", 3)'], 'E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3') +enddef + def Test_cursor() new setline(1, range(4)) @@ -1144,6 +1161,11 @@ def Test_getbufline() CheckDefAndScriptFailure2(['getbufline("a", 2, 0z10)'], 'E1013: Argument 3: type mismatch, expected string but got blob', 'E1174: String required for argument 3') enddef +def Test_getbufvar() + CheckDefAndScriptFailure2(['getbufvar(true, "v")'], 'E1013: Argument 1: type mismatch, expected string but got bool', 'E1174: String required for argument 1') + CheckDefAndScriptFailure2(['getbufvar(1, 2, 3)'], 'E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2') +enddef + def Test_getchangelist() new setline(1, 'some text') @@ -1862,8 +1884,7 @@ enddef def Test_matchaddpos() CheckDefAndScriptFailure2(['matchaddpos(1, [1])'], 'E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1') - CheckDefAndScriptFailure2(['matchaddpos("a", "b")'], 'E1013: Argument 2: type mismatch, expected list but got string', 'E1211: List required for argument 2') - CheckDefFailure(['matchaddpos("a", ["2"])'], 'E1013: Argument 2: type mismatch, expected list but got list') + CheckDefAndScriptFailure2(['matchaddpos("a", "b")'], 'E1013: Argument 2: type mismatch, expected list but got string', 'E1211: List required for argument 2') CheckDefAndScriptFailure2(['matchaddpos("a", [1], "c")'], 'E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3') CheckDefAndScriptFailure2(['matchaddpos("a", [1], 1, "d")'], 'E1013: Argument 4: type mismatch, expected number but got string', 'E1210: Number required for argument 4') CheckDefAndScriptFailure2(['matchaddpos("a", [1], 1, 1, [])'], 'E1013: Argument 5: type mismatch, expected dict but got list', 'E1206: Dictionary required for argument 5') @@ -2148,6 +2169,22 @@ def Test_prompt_getprompt() endif enddef +def Test_prompt_setcallback() + if !has('channel') + CheckFeature channel + else + CheckDefAndScriptFailure2(['prompt_setcallback(true, "1")'], 'E1013: Argument 1: type mismatch, expected string but got bool', 'E1174: String required for argument 1') + endif +enddef + +def Test_prompt_setinterrupt() + if !has('channel') + CheckFeature channel + else + CheckDefAndScriptFailure2(['prompt_setinterrupt(true, "1")'], 'E1013: Argument 1: type mismatch, expected string but got bool', 'E1174: String required for argument 1') + endif +enddef + def Test_prompt_setprompt() if !has('channel') CheckFeature channel @@ -2575,13 +2612,20 @@ def Test_searchpair() lines =<< trim END def TestPair() - echo searchpair("a", "b", "c", "d", "1", "f") + echo searchpair("a", "b", "c", "d", "1", 99) enddef defcompile END CheckScriptSuccess(lines) bwipe! + CheckDefAndScriptFailure2(['searchpair(1, "b", "c")'], 'E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1') + CheckDefAndScriptFailure2(['searchpair("a", 2, "c")'], 'E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2') + CheckDefAndScriptFailure2(['searchpair("a", "b", 3)'], 'E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3') + CheckDefAndScriptFailure2(['searchpair("a", "b", "c", 4)'], 'E1013: Argument 4: type mismatch, expected string but got number', 'E1174: String required for argument 4') + # BUG: Vim crashes with the following test + #CheckDefAndScriptFailure2(['searchpair("a", "b", "c", "d", "1", "f")'], 'E1013: Argument 4: type mismatch, expected string but got number', 'E1174: String required for argument 4') + #CheckDefAndScriptFailure2(['searchpair("a", "b", "c", "d", "1", 3, "g")'], 'E1013: Argument 4: type mismatch, expected string but got number', 'E1174: String required for argument 4') enddef def Test_searchpos() @@ -2676,6 +2720,9 @@ def Test_setbufvar() setbufvar('%', 'myvar', 123) getbufvar('%', 'myvar')->assert_equal(123) + + CheckDefAndScriptFailure2(['setbufvar(true, "v", 3)'], 'E1013: Argument 1: type mismatch, expected string but got bool', 'E1174: String required for argument 1') + CheckDefAndScriptFailure2(['setbufvar(1, 2, 3)'], 'E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2') enddef def Test_setbufline() @@ -3112,6 +3159,12 @@ def Test_synID() CheckDefAndScriptFailure2(['synID(1, 1, 2)'], 'E1013: Argument 3: type mismatch, expected bool but got number', 'E1212: Bool required for argument 3') enddef +def Test_synIDattr() + CheckDefAndScriptFailure2(['synIDattr("a", "b")'], 'E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1') + CheckDefAndScriptFailure2(['synIDattr(1, 2)'], 'E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2') + CheckDefAndScriptFailure2(['synIDattr(1, "b", 3)'], 'E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3') +enddef + def Test_synIDtrans() CheckDefFailure(['synIDtrans("a")'], 'E1013: Argument 1: type mismatch, expected number but got string') enddef @@ -3379,6 +3432,11 @@ def Test_timer_paused() timer_stop(id) enddef +def Test_timer_start() + CheckDefAndScriptFailure2(['timer_start("a", "1")'], 'E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1') + CheckDefAndScriptFailure2(['timer_start(1, "1", [1])'], 'E1013: Argument 3: type mismatch, expected dict but got list', 'E1206: Dictionary required for argument 3') +enddef + def Test_timer_stop() CheckDefFailure(['timer_stop("x")'], 'E1013: Argument 1: type mismatch, expected number but got string') assert_equal(0, timer_stop(100)) diff --git a/src/time.c b/src/time.c --- a/src/time.c +++ b/src/time.c @@ -799,7 +799,7 @@ f_timer_pause(typval_T *argvars, typval_ void f_timer_start(typval_T *argvars, typval_T *rettv) { - long msec = (long)tv_get_number(&argvars[0]); + long msec; timer_T *timer; int repeat = 0; callback_T callback; @@ -808,6 +808,13 @@ f_timer_start(typval_T *argvars, typval_ rettv->vval.v_number = -1; if (check_secure()) return; + + if (in_vim9script() + && (check_for_number_arg(argvars, 0) == FAIL + || check_for_opt_dict_arg(argvars, 2) == FAIL)) + return; + + msec = (long)tv_get_number(&argvars[0]); if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_DICT diff --git a/src/typval.c b/src/typval.c --- a/src/typval.c +++ b/src/typval.c @@ -523,6 +523,17 @@ check_for_chan_or_job_arg(typval_T *args } /* + * Give an error and return FAIL unless "args[idx]" is an optional channel or a + * job. + */ + int +check_for_opt_chan_or_job_arg(typval_T *args, int idx) +{ + return (args[idx].v_type == VAR_UNKNOWN + || check_for_chan_or_job_arg(args, idx) != FAIL); +} + +/* * Give an error and return FAIL unless "args[idx]" is a job. */ int diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -756,6 +756,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3211, +/**/ 3210, /**/ 3209,