# HG changeset patch # User Bram Moolenaar # Date 1586099725 -7200 # Node ID d4fa9db88d16c5c43104fe754fbd04f36ee8d36f # Parent 70edfdef3b8334e8feeba35c9e1314d2f9aadec8 patch 8.2.0512: Vim9: no optional arguments in func type Commit: https://github.com/vim/vim/commit/5deeb3f1f9db4eabd36e99cbf857fe376eb37e10 Author: Bram Moolenaar Date: Sun Apr 5 17:08:17 2020 +0200 patch 8.2.0512: Vim9: no optional arguments in func type Problem: Vim9: no optional arguments in func type. Solution: Check for question mark after type. Find function reference without function(). diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -379,39 +379,39 @@ EXTERN sctx_T current_sctx INIT4(0, 0, 0 // Commonly used types. -EXTERN type_T t_any INIT5(VAR_UNKNOWN, 0, 0, NULL, NULL); -EXTERN type_T t_void INIT5(VAR_VOID, 0, 0, NULL, NULL); -EXTERN type_T t_bool INIT5(VAR_BOOL, 0, 0, NULL, NULL); -EXTERN type_T t_special INIT5(VAR_SPECIAL, 0, 0, NULL, NULL); -EXTERN type_T t_number INIT5(VAR_NUMBER, 0, 0, NULL, NULL); -EXTERN type_T t_float INIT5(VAR_FLOAT, 0, 0, NULL, NULL); -EXTERN type_T t_string INIT5(VAR_STRING, 0, 0, NULL, NULL); -EXTERN type_T t_blob INIT5(VAR_BLOB, 0, 0, NULL, NULL); -EXTERN type_T t_job INIT5(VAR_JOB, 0, 0, NULL, NULL); -EXTERN type_T t_channel INIT5(VAR_CHANNEL, 0, 0, NULL, NULL); +EXTERN type_T t_any INIT6(VAR_UNKNOWN, 0, 0, 0, NULL, NULL); +EXTERN type_T t_void INIT6(VAR_VOID, 0, 0, 0, NULL, NULL); +EXTERN type_T t_bool INIT6(VAR_BOOL, 0, 0, 0, NULL, NULL); +EXTERN type_T t_special INIT6(VAR_SPECIAL, 0, 0, 0, NULL, NULL); +EXTERN type_T t_number INIT6(VAR_NUMBER, 0, 0, 0, NULL, NULL); +EXTERN type_T t_float INIT6(VAR_FLOAT, 0, 0, 0, NULL, NULL); +EXTERN type_T t_string INIT6(VAR_STRING, 0, 0, 0, NULL, NULL); +EXTERN type_T t_blob INIT6(VAR_BLOB, 0, 0, 0, NULL, NULL); +EXTERN type_T t_job INIT6(VAR_JOB, 0, 0, 0, NULL, NULL); +EXTERN type_T t_channel INIT6(VAR_CHANNEL, 0, 0, 0, NULL, NULL); -EXTERN type_T t_func_void INIT5(VAR_FUNC, -1, 0, &t_void, NULL); -EXTERN type_T t_func_any INIT5(VAR_FUNC, -1, 0, &t_any, NULL); -EXTERN type_T t_func_number INIT5(VAR_FUNC, -1, 0, &t_number, NULL); -EXTERN type_T t_func_string INIT5(VAR_FUNC, -1, 0, &t_string, NULL); -EXTERN type_T t_func_0_void INIT5(VAR_FUNC, 0, 0, &t_void, NULL); -EXTERN type_T t_func_0_any INIT5(VAR_FUNC, 0, 0, &t_any, NULL); -EXTERN type_T t_func_0_number INIT5(VAR_FUNC, 0, 0, &t_number, NULL); -EXTERN type_T t_func_0_string INIT5(VAR_FUNC, 0, 0, &t_string, NULL); +EXTERN type_T t_func_void INIT6(VAR_FUNC, -1, 0, 0, &t_void, NULL); +EXTERN type_T t_func_any INIT6(VAR_FUNC, -1, 0, 0, &t_any, NULL); +EXTERN type_T t_func_number INIT6(VAR_FUNC, -1, 0, 0, &t_number, NULL); +EXTERN type_T t_func_string INIT6(VAR_FUNC, -1, 0, 0, &t_string, NULL); +EXTERN type_T t_func_0_void INIT6(VAR_FUNC, 0, 0, 0, &t_void, NULL); +EXTERN type_T t_func_0_any INIT6(VAR_FUNC, 0, 0, 0, &t_any, NULL); +EXTERN type_T t_func_0_number INIT6(VAR_FUNC, 0, 0, 0, &t_number, NULL); +EXTERN type_T t_func_0_string INIT6(VAR_FUNC, 0, 0, 0, &t_string, NULL); -EXTERN type_T t_list_any INIT5(VAR_LIST, 0, 0, &t_any, NULL); -EXTERN type_T t_dict_any INIT5(VAR_DICT, 0, 0, &t_any, NULL); -EXTERN type_T t_list_empty INIT5(VAR_LIST, 0, 0, &t_void, NULL); -EXTERN type_T t_dict_empty INIT5(VAR_DICT, 0, 0, &t_void, NULL); +EXTERN type_T t_list_any INIT6(VAR_LIST, 0, 0, 0, &t_any, NULL); +EXTERN type_T t_dict_any INIT6(VAR_DICT, 0, 0, 0, &t_any, NULL); +EXTERN type_T t_list_empty INIT6(VAR_LIST, 0, 0, 0, &t_void, NULL); +EXTERN type_T t_dict_empty INIT6(VAR_DICT, 0, 0, 0, &t_void, NULL); -EXTERN type_T t_list_bool INIT5(VAR_LIST, 0, 0, &t_bool, NULL); -EXTERN type_T t_list_number INIT5(VAR_LIST, 0, 0, &t_number, NULL); -EXTERN type_T t_list_string INIT5(VAR_LIST, 0, 0, &t_string, NULL); -EXTERN type_T t_list_dict_any INIT5(VAR_LIST, 0, 0, &t_dict_any, NULL); +EXTERN type_T t_list_bool INIT6(VAR_LIST, 0, 0, 0, &t_bool, NULL); +EXTERN type_T t_list_number INIT6(VAR_LIST, 0, 0, 0, &t_number, NULL); +EXTERN type_T t_list_string INIT6(VAR_LIST, 0, 0, 0, &t_string, NULL); +EXTERN type_T t_list_dict_any INIT6(VAR_LIST, 0, 0, 0, &t_dict_any, NULL); -EXTERN type_T t_dict_bool INIT5(VAR_DICT, 0, 0, &t_bool, NULL); -EXTERN type_T t_dict_number INIT5(VAR_DICT, 0, 0, &t_number, NULL); -EXTERN type_T t_dict_string INIT5(VAR_DICT, 0, 0, &t_string, NULL); +EXTERN type_T t_dict_bool INIT6(VAR_DICT, 0, 0, 0, &t_bool, NULL); +EXTERN type_T t_dict_number INIT6(VAR_DICT, 0, 0, 0, &t_number, NULL); +EXTERN type_T t_dict_string INIT6(VAR_DICT, 0, 0, 0, &t_string, NULL); #endif diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1341,10 +1341,11 @@ typedef enum typedef struct type_S type_T; struct type_S { vartype_T tt_type; - short tt_argcount; // for func, partial, -1 for unknown - short tt_flags; // TTFLAG_ values + char tt_argcount; // for func, -1 for unknown + char tt_min_argcount; // number of non-optional arguments + char tt_flags; // TTFLAG_ values type_T *tt_member; // for list, dict, func return type - type_T **tt_args; // func arguments, allocated + type_T **tt_args; // func argument types, allocated }; #define TTFLAG_VARARGS 1 // func args ends with "..." @@ -1520,7 +1521,7 @@ typedef struct int uf_calls; // nr of active calls int uf_cleared; // func_clear() was already called int uf_dfunc_idx; // >= 0 for :def function only - garray_T uf_args; // arguments + garray_T uf_args; // arguments, including optional arguments garray_T uf_def_args; // default argument expressions // for :def (for :function uf_ret_type is NULL) @@ -1531,6 +1532,7 @@ typedef struct // uf_def_args; length: uf_def_args.ga_len + 1 char_u *uf_va_name; // name from "...name" or NULL type_T *uf_va_type; // type from "...name: type" or NULL + type_T *uf_func_type; // type of the function, &t_func_any if unknown garray_T uf_lines; // function lines # ifdef FEAT_PROFILE diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -274,6 +274,7 @@ NEW_TESTS = \ test_vartabs \ test_vim9_disassemble \ test_vim9_expr \ + test_vim9_func \ test_vim9_script \ test_viminfo \ test_vimscript \ @@ -483,6 +484,7 @@ NEW_TESTS_RES = \ test_vartabs.res \ test_vim9_disassemble.res \ test_vim9_expr.res \ + test_vim9_func.res \ test_vim9_script.res \ test_viminfo.res \ test_vimscript.res \ diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -87,8 +87,10 @@ enddef def Test_disassemble_store() let res = execute('disass s:ScriptFuncStore') assert_match('\d*_ScriptFuncStore.*' + \ .. 'let localnr = 1.*' \ .. 'localnr = 2.*' \ .. ' STORE 2 in $0.*' + \ .. 'let localstr = ''abc''.*' \ .. 'localstr = ''xyz''.*' \ .. ' STORE $1.*' \ .. 'v:char = ''abc''.*' @@ -360,22 +362,22 @@ def Test_disassemble_const_expr() enddef def WithFunc() - let funky1: func - let funky2: func = function("len") - let party2: func = funcref("UserFunc") + let Funky1: func + let Funky2: func = function("len") + let Party2: func = funcref("UserFunc") enddef def Test_disassemble_function() let instr = execute('disassemble WithFunc') assert_match('WithFunc.*' - \ .. 'let funky1: func.*' + \ .. 'let Funky1: func.*' \ .. '0 PUSHFUNC "\[none]".*' \ .. '1 STORE $0.*' - \ .. 'let funky2: func = function("len").*' + \ .. 'let Funky2: func = function("len").*' \ .. '2 PUSHS "len".*' \ .. '3 BCALL function(argc 1).*' \ .. '4 STORE $1.*' - \ .. 'let party2: func = funcref("UserFunc").*' + \ .. 'let Party2: func = funcref("UserFunc").*' \ .. '\d PUSHS "UserFunc".*' \ .. '\d BCALL funcref(argc 1).*' \ .. '\d STORE $2.*' diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -460,8 +460,8 @@ func Test_expr4_fails() call CheckDefFailureMult(['let j: job', 'let chan: channel', 'let r = j == chan'], 'Cannot compare job with channel') call CheckDefFailureMult(['let j: job', 'let x: list', 'let r = j == x'], 'Cannot compare job with list') - call CheckDefFailureMult(['let j: job', 'let x: func', 'let r = j == x'], 'Cannot compare job with func') - call CheckDefFailureMult(['let j: job', 'let x: func', 'let r = j == x'], 'Cannot compare job with func') + call CheckDefFailureMult(['let j: job', 'let Xx: func', 'let r = j == Xx'], 'Cannot compare job with func') + call CheckDefFailureMult(['let j: job', 'let Xx: func', 'let r = j == Xx'], 'Cannot compare job with func') endfunc " test addition, subtraction, concatenation diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim new file mode 100644 --- /dev/null +++ b/src/testdir/test_vim9_func.vim @@ -0,0 +1,400 @@ +" Test various aspects of the Vim9 script language. + +source check.vim +source view_util.vim + +" Check that "lines" inside ":def" results in an "error" message. +func CheckDefFailure(lines, error) + call writefile(['def Func()'] + a:lines + ['enddef'], 'Xdef') + call assert_fails('so Xdef', a:error, a:lines) + call delete('Xdef') +endfunc + +func CheckScriptFailure(lines, error) + call writefile(a:lines, 'Xdef') + call assert_fails('so Xdef', a:error, a:lines) + call delete('Xdef') +endfunc + +func Test_def_basic() + def SomeFunc(): string + return 'yes' + enddef + call assert_equal('yes', SomeFunc()) +endfunc + +def ReturnString(): string + return 'string' +enddef + +def ReturnNumber(): number + return 123 +enddef + +let g:notNumber = 'string' + +def ReturnGlobal(): number + return g:notNumber +enddef + +def Test_return_something() + assert_equal('string', ReturnString()) + assert_equal(123, ReturnNumber()) + assert_fails('call ReturnGlobal()', 'E1029: Expected number but got string') +enddef + +let s:nothing = 0 +def ReturnNothing() + s:nothing = 1 + if true + return + endif + s:nothing = 2 +enddef + +def Test_return_nothing() + ReturnNothing() + assert_equal(1, s:nothing) +enddef + +func Increment() + let g:counter += 1 +endfunc + +def Test_call_ufunc_count() + g:counter = 1 + Increment() + Increment() + Increment() + " works with and without :call + assert_equal(4, g:counter) + call assert_equal(4, g:counter) + unlet g:counter +enddef + +def MyVarargs(arg: string, ...rest: list): string + let res = arg + for s in rest + res ..= ',' .. s + endfor + return res +enddef + +def Test_call_varargs() + assert_equal('one', MyVarargs('one')) + assert_equal('one,two', MyVarargs('one', 'two')) + assert_equal('one,two,three', MyVarargs('one', 'two', 'three')) +enddef + +def MyDefaultArgs(name = 'string'): string + return name +enddef + +def Test_call_default_args() + assert_equal('string', MyDefaultArgs()) + assert_equal('one', MyDefaultArgs('one')) + assert_fails('call MyDefaultArgs("one", "two")', 'E118:') + + call CheckScriptFailure(['def Func(arg: number = asdf)', 'enddef'], 'E1001:') +enddef + +func Test_call_default_args_from_func() + call assert_equal('string', MyDefaultArgs()) + call assert_equal('one', MyDefaultArgs('one')) + call assert_fails('call MyDefaultArgs("one", "two")', 'E118:') +endfunc + +func TakesOneArg(arg) + echo a:arg +endfunc + +def Test_call_wrong_args() + call CheckDefFailure(['TakesOneArg()'], 'E119:') + call CheckDefFailure(['TakesOneArg(11, 22)'], 'E118:') + call CheckDefFailure(['bufnr(xxx)'], 'E1001:') +enddef + +" Default arg and varargs +def MyDefVarargs(one: string, two = 'foo', ...rest: list): string + let res = one .. ',' .. two + for s in rest + res ..= ',' .. s + endfor + return res +enddef + +def Test_call_def_varargs() + call assert_fails('call MyDefVarargs()', 'E119:') + assert_equal('one,foo', MyDefVarargs('one')) + assert_equal('one,two', MyDefVarargs('one', 'two')) + assert_equal('one,two,three', MyDefVarargs('one', 'two', 'three')) +enddef + +def Test_using_var_as_arg() + call writefile(['def Func(x: number)', 'let x = 234', 'enddef'], 'Xdef') + call assert_fails('so Xdef', 'E1006:') + call delete('Xdef') +enddef + +def Test_call_func_defined_later() + call assert_equal('one', DefinedLater('one')) + call assert_fails('call NotDefined("one")', 'E117:') +enddef + +func DefinedLater(arg) + return a:arg +endfunc + +def FuncWithForwardCall() + return DefinedEvenLater("yes") +enddef + +def DefinedEvenLater(arg: string): string + return arg +enddef + +def Test_error_in_nested_function() + " Error in called function requires unwinding the call stack. + assert_fails('call FuncWithForwardCall()', 'E1029') +enddef + +def Test_return_type_wrong() + CheckScriptFailure(['def Func(): number', 'return "a"', 'enddef'], 'expected number but got string') + CheckScriptFailure(['def Func(): string', 'return 1', 'enddef'], 'expected string but got number') + CheckScriptFailure(['def Func(): void', 'return "a"', 'enddef'], 'expected void but got string') + CheckScriptFailure(['def Func()', 'return "a"', 'enddef'], 'expected void but got string') + + CheckScriptFailure(['def Func(): number', 'return', 'enddef'], 'E1003:') + + CheckScriptFailure(['def Func(): list', 'return []', 'enddef'], 'E1008:') + CheckScriptFailure(['def Func(): dict', 'return {}', 'enddef'], 'E1008:') +enddef + +def Test_arg_type_wrong() + CheckScriptFailure(['def Func3(items: list)', 'echo "a"', 'enddef'], 'E1008: Missing ') +enddef + +def Test_vim9script_call() + let lines =<< trim END + vim9script + let var = '' + def MyFunc(arg: string) + var = arg + enddef + MyFunc('foobar') + assert_equal('foobar', var) + + let str = 'barfoo' + str->MyFunc() + assert_equal('barfoo', var) + + let g:value = 'value' + g:value->MyFunc() + assert_equal('value', var) + + let listvar = [] + def ListFunc(arg: list) + listvar = arg + enddef + [1, 2, 3]->ListFunc() + assert_equal([1, 2, 3], listvar) + + let dictvar = {} + def DictFunc(arg: dict) + dictvar = arg + enddef + {'a': 1, 'b': 2}->DictFunc() + assert_equal(#{a: 1, b: 2}, dictvar) + def CompiledDict() + {'a': 3, 'b': 4}->DictFunc() + enddef + CompiledDict() + assert_equal(#{a: 3, b: 4}, dictvar) + + #{a: 3, b: 4}->DictFunc() + assert_equal(#{a: 3, b: 4}, dictvar) + + ('text')->MyFunc() + assert_equal('text', var) + ("some")->MyFunc() + assert_equal('some', var) + END + writefile(lines, 'Xcall.vim') + source Xcall.vim + delete('Xcall.vim') +enddef + +def Test_vim9script_call_fail_decl() + let lines =<< trim END + vim9script + let var = '' + def MyFunc(arg: string) + let var = 123 + enddef + END + writefile(lines, 'Xcall_decl.vim') + assert_fails('source Xcall_decl.vim', 'E1054:') + delete('Xcall_decl.vim') +enddef + +def Test_vim9script_call_fail_const() + let lines =<< trim END + vim9script + const var = '' + def MyFunc(arg: string) + var = 'asdf' + enddef + END + writefile(lines, 'Xcall_const.vim') + assert_fails('source Xcall_const.vim', 'E46:') + delete('Xcall_const.vim') +enddef + +" Test that inside :function a Python function can be defined, :def is not +" recognized. +func Test_function_python() + CheckFeature python3 + let py = 'python3' + execute py "<< EOF" +def do_something(): + return 1 +EOF +endfunc + +def Test_delfunc() + let lines =<< trim END + vim9script + def GoneSoon() + echo 'hello' + enddef + + def CallGoneSoon() + GoneSoon() + enddef + + delfunc GoneSoon + CallGoneSoon() + END + writefile(lines, 'XToDelFunc') + assert_fails('so XToDelFunc', 'E933') + assert_fails('so XToDelFunc', 'E933') + + delete('XToDelFunc') +enddef + +def Test_redef_failure() + call writefile(['def Func0(): string', 'return "Func0"', 'enddef'], 'Xdef') + so Xdef + call writefile(['def Func1(): string', 'return "Func1"', 'enddef'], 'Xdef') + so Xdef + call writefile(['def! Func0(): string', 'enddef'], 'Xdef') + call assert_fails('so Xdef', 'E1027:') + call writefile(['def Func2(): string', 'return "Func2"', 'enddef'], 'Xdef') + so Xdef + call delete('Xdef') + + call assert_equal(0, Func0()) + call assert_equal('Func1', Func1()) + call assert_equal('Func2', Func2()) + + delfunc! Func0 + delfunc! Func1 + delfunc! Func2 +enddef + +" Test for internal functions returning different types +func Test_InternalFuncRetType() + let lines =<< trim END + def RetFloat(): float + return ceil(1.456) + enddef + + def RetListAny(): list + return items({'k' : 'v'}) + enddef + + def RetListString(): list + return split('a:b:c', ':') + enddef + + def RetListDictAny(): list> + return getbufinfo() + enddef + + def RetDictNumber(): dict + return wordcount() + enddef + + def RetDictString(): dict + return environ() + enddef + END + call writefile(lines, 'Xscript') + source Xscript + + call assert_equal(2.0, RetFloat()) + call assert_equal([['k', 'v']], RetListAny()) + call assert_equal(['a', 'b', 'c'], RetListString()) + call assert_notequal([], RetListDictAny()) + call assert_notequal({}, RetDictNumber()) + call assert_notequal({}, RetDictString()) + call delete('Xscript') +endfunc + +" Test for passing too many or too few arguments to internal functions +func Test_internalfunc_arg_error() + let l =<< trim END + def! FArgErr(): float + return ceil(1.1, 2) + enddef + END + call writefile(l, 'Xinvalidarg') + call assert_fails('so Xinvalidarg', 'E118:') + let l =<< trim END + def! FArgErr(): float + return ceil() + enddef + END + call writefile(l, 'Xinvalidarg') + call assert_fails('so Xinvalidarg', 'E119:') + call delete('Xinvalidarg') +endfunc + +let s:funcResult = 0 + +def FuncNoArgNoRet() + funcResult = 11 +enddef + +def FuncNoArgRetNumber(): number + funcResult = 22 + return 1234 +enddef + +def FuncOneArgNoRet(arg: number) + funcResult = arg +enddef + +def FuncOneArgRetNumber(arg: number): number + funcResult = arg + return arg +enddef + +def Test_func_type() + let Ref1: func() + funcResult = 0 + Ref1 = FuncNoArgNoRet + Ref1() + assert_equal(11, funcResult) +enddef + +def Test_func_type_fails() + CheckDefFailure(['let ref1: func()'], 'E704:') + + CheckDefFailure(['let Ref1: func()', 'Ref1 = FuncNoArgRetNumber'], 'E1013: type mismatch, expected func() but got func(): number') + CheckDefFailure(['let Ref1: func()', 'Ref1 = FuncOneArgNoRet'], 'E1013: type mismatch, expected func() but got func(number)') + CheckDefFailure(['let Ref1: func()', 'Ref1 = FuncOneArgRetNumber'], 'E1013: type mismatch, expected func() but got func(number): number') +enddef + + +" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -21,13 +21,6 @@ def Test_syntax() let other: list = ['asdf'] enddef -func Test_def_basic() - def SomeFunc(): string - return 'yes' - enddef - call assert_equal('yes', SomeFunc()) -endfunc - let s:appendToMe = 'xxx' let s:addToMe = 111 let g:existing = 'yes' @@ -66,9 +59,9 @@ def Test_assignment() if has('float') let float1: float = 3.4 endif - let funky1: func - let funky2: func = function('len') - let party2: func = funcref('Test_syntax') + let Funky1: func + let Funky2: func = function('len') + let Party2: func = funcref('Test_syntax') " type becomes list let somelist = rand() > 0 ? [1, 2, 3] : ['a', 'b', 'c'] @@ -134,6 +127,9 @@ def Test_assignment() assert_equal('noneagain', v:errmsg) call CheckDefFailure(['v:errmsg += "more"'], 'E1013:') call CheckDefFailure(['v:errmsg += 123'], 'E1013:') +enddef + +def Test_assignment_default() " Test default values. let thebool: bool @@ -153,8 +149,8 @@ def Test_assignment() let theblob: blob assert_equal(0z, theblob) - let thefunc: func - assert_equal(test_null_function(), thefunc) + let Thefunc: func + assert_equal(test_null_function(), Thefunc) let thelist: list assert_equal([], thelist) @@ -264,157 +260,6 @@ def Test_cmd_modifier() enddef -def ReturnString(): string - return 'string' -enddef - -def ReturnNumber(): number - return 123 -enddef - -let g:notNumber = 'string' - -def ReturnGlobal(): number - return g:notNumber -enddef - -def Test_return_something() - assert_equal('string', ReturnString()) - assert_equal(123, ReturnNumber()) - assert_fails('call ReturnGlobal()', 'E1029: Expected number but got string') -enddef - -let s:nothing = 0 -def ReturnNothing() - s:nothing = 1 - if true - return - endif - s:nothing = 2 -enddef - -def Test_return_nothing() - ReturnNothing() - assert_equal(1, s:nothing) -enddef - -func Increment() - let g:counter += 1 -endfunc - -def Test_call_ufunc_count() - g:counter = 1 - Increment() - Increment() - Increment() - " works with and without :call - assert_equal(4, g:counter) - call assert_equal(4, g:counter) - unlet g:counter -enddef - -def MyVarargs(arg: string, ...rest: list): string - let res = arg - for s in rest - res ..= ',' .. s - endfor - return res -enddef - -def Test_call_varargs() - assert_equal('one', MyVarargs('one')) - assert_equal('one,two', MyVarargs('one', 'two')) - assert_equal('one,two,three', MyVarargs('one', 'two', 'three')) -enddef - -def MyDefaultArgs(name = 'string'): string - return name -enddef - -def Test_call_default_args() - assert_equal('string', MyDefaultArgs()) - assert_equal('one', MyDefaultArgs('one')) - assert_fails('call MyDefaultArgs("one", "two")', 'E118:') - - call CheckScriptFailure(['def Func(arg: number = asdf)', 'enddef'], 'E1001:') -enddef - -func Test_call_default_args_from_func() - call assert_equal('string', MyDefaultArgs()) - call assert_equal('one', MyDefaultArgs('one')) - call assert_fails('call MyDefaultArgs("one", "two")', 'E118:') -endfunc - -func TakesOneArg(arg) - echo a:arg -endfunc - -def Test_call_wrong_args() - call CheckDefFailure(['TakesOneArg()'], 'E119:') - call CheckDefFailure(['TakesOneArg(11, 22)'], 'E118:') - call CheckDefFailure(['bufnr(xxx)'], 'E1001:') -enddef - -" Default arg and varargs -def MyDefVarargs(one: string, two = 'foo', ...rest: list): string - let res = one .. ',' .. two - for s in rest - res ..= ',' .. s - endfor - return res -enddef - -def Test_call_def_varargs() - call assert_fails('call MyDefVarargs()', 'E119:') - assert_equal('one,foo', MyDefVarargs('one')) - assert_equal('one,two', MyDefVarargs('one', 'two')) - assert_equal('one,two,three', MyDefVarargs('one', 'two', 'three')) -enddef - -def Test_using_var_as_arg() - call writefile(['def Func(x: number)', 'let x = 234', 'enddef'], 'Xdef') - call assert_fails('so Xdef', 'E1006:') - call delete('Xdef') -enddef - -def Test_call_func_defined_later() - call assert_equal('one', DefinedLater('one')) - call assert_fails('call NotDefined("one")', 'E117:') -enddef - -func DefinedLater(arg) - return a:arg -endfunc - -def FuncWithForwardCall() - return DefinedEvenLater("yes") -enddef - -def DefinedEvenLater(arg: string): string - return arg -enddef - -def Test_error_in_nested_function() - " Error in called function requires unwinding the call stack. - assert_fails('call FuncWithForwardCall()', 'E1029') -enddef - -def Test_return_type_wrong() - CheckScriptFailure(['def Func(): number', 'return "a"', 'enddef'], 'expected number but got string') - CheckScriptFailure(['def Func(): string', 'return 1', 'enddef'], 'expected string but got number') - CheckScriptFailure(['def Func(): void', 'return "a"', 'enddef'], 'expected void but got string') - CheckScriptFailure(['def Func()', 'return "a"', 'enddef'], 'expected void but got string') - - CheckScriptFailure(['def Func(): number', 'return', 'enddef'], 'E1003:') - - CheckScriptFailure(['def Func(): list', 'return []', 'enddef'], 'E1008:') - CheckScriptFailure(['def Func(): dict', 'return {}', 'enddef'], 'E1008:') -enddef - -def Test_arg_type_wrong() - CheckScriptFailure(['def Func3(items: list)', 'echo "a"', 'enddef'], 'E1008: Missing ') -enddef - def Test_try_catch() let l = [] try @@ -732,82 +577,6 @@ def Test_vim9script_fails() assert_fails('export something', 'E1042') enddef -def Test_vim9script_call() - let lines =<< trim END - vim9script - let var = '' - def MyFunc(arg: string) - var = arg - enddef - MyFunc('foobar') - assert_equal('foobar', var) - - let str = 'barfoo' - str->MyFunc() - assert_equal('barfoo', var) - - let g:value = 'value' - g:value->MyFunc() - assert_equal('value', var) - - let listvar = [] - def ListFunc(arg: list) - listvar = arg - enddef - [1, 2, 3]->ListFunc() - assert_equal([1, 2, 3], listvar) - - let dictvar = {} - def DictFunc(arg: dict) - dictvar = arg - enddef - {'a': 1, 'b': 2}->DictFunc() - assert_equal(#{a: 1, b: 2}, dictvar) - def CompiledDict() - {'a': 3, 'b': 4}->DictFunc() - enddef - CompiledDict() - assert_equal(#{a: 3, b: 4}, dictvar) - - #{a: 3, b: 4}->DictFunc() - assert_equal(#{a: 3, b: 4}, dictvar) - - ('text')->MyFunc() - assert_equal('text', var) - ("some")->MyFunc() - assert_equal('some', var) - END - writefile(lines, 'Xcall.vim') - source Xcall.vim - delete('Xcall.vim') -enddef - -def Test_vim9script_call_fail_decl() - let lines =<< trim END - vim9script - let var = '' - def MyFunc(arg: string) - let var = 123 - enddef - END - writefile(lines, 'Xcall_decl.vim') - assert_fails('source Xcall_decl.vim', 'E1054:') - delete('Xcall_decl.vim') -enddef - -def Test_vim9script_call_fail_const() - let lines =<< trim END - vim9script - const var = '' - def MyFunc(arg: string) - var = 'asdf' - enddef - END - writefile(lines, 'Xcall_const.vim') - assert_fails('source Xcall_const.vim', 'E46:') - delete('Xcall_const.vim') -enddef - def Test_vim9script_reload() let lines =<< trim END vim9script @@ -926,17 +695,6 @@ def Test_fixed_size_list() assert_equal([2, 99, 3, 4, 5], l) enddef -" Test that inside :function a Python function can be defined, :def is not -" recognized. -func Test_function_python() - CheckFeature python3 - let py = 'python3' - execute py "<< EOF" -def do_something(): - return 1 -EOF -endfunc - def IfElse(what: number): string let res = '' if what == 1 @@ -1087,27 +845,6 @@ def Test_if_const_expr_fails() call CheckDefFailure(["if has('aaa') ? true false"], 'E109:') enddef -def Test_delfunc() - let lines =<< trim END - vim9script - def GoneSoon() - echo 'hello' - enddef - - def CallGoneSoon() - GoneSoon() - enddef - - delfunc GoneSoon - CallGoneSoon() - END - writefile(lines, 'XToDelFunc') - assert_fails('so XToDelFunc', 'E933') - assert_fails('so XToDelFunc', 'E933') - - delete('XToDelFunc') -enddef - def Test_execute_cmd() new setline(1, 'default') @@ -1222,83 +959,6 @@ def Test_interrupt_loop() assert_true(caught, 'should have caught an exception') enddef -def Test_redef_failure() - call writefile(['def Func0(): string', 'return "Func0"', 'enddef'], 'Xdef') - so Xdef - call writefile(['def Func1(): string', 'return "Func1"', 'enddef'], 'Xdef') - so Xdef - call writefile(['def! Func0(): string', 'enddef'], 'Xdef') - call assert_fails('so Xdef', 'E1027:') - call writefile(['def Func2(): string', 'return "Func2"', 'enddef'], 'Xdef') - so Xdef - call delete('Xdef') - - call assert_equal(0, Func0()) - call assert_equal('Func1', Func1()) - call assert_equal('Func2', Func2()) - - delfunc! Func0 - delfunc! Func1 - delfunc! Func2 -enddef - -" Test for internal functions returning different types -func Test_InternalFuncRetType() - let lines =<< trim END - def RetFloat(): float - return ceil(1.456) - enddef - - def RetListAny(): list - return items({'k' : 'v'}) - enddef - - def RetListString(): list - return split('a:b:c', ':') - enddef - - def RetListDictAny(): list> - return getbufinfo() - enddef - - def RetDictNumber(): dict - return wordcount() - enddef - - def RetDictString(): dict - return environ() - enddef - END - call writefile(lines, 'Xscript') - source Xscript - - call assert_equal(2.0, RetFloat()) - call assert_equal([['k', 'v']], RetListAny()) - call assert_equal(['a', 'b', 'c'], RetListString()) - call assert_notequal([], RetListDictAny()) - call assert_notequal({}, RetDictNumber()) - call assert_notequal({}, RetDictString()) - call delete('Xscript') -endfunc - -" Test for passing too many or too few arguments to internal functions -func Test_internalfunc_arg_error() - let l =<< trim END - def! FArgErr(): float - return ceil(1.1, 2) - enddef - END - call writefile(l, 'Xinvalidarg') - call assert_fails('so Xinvalidarg', 'E118:') - let l =<< trim END - def! FArgErr(): float - return ceil() - enddef - END - call writefile(l, 'Xinvalidarg') - call assert_fails('so Xinvalidarg', 'E119:') - call delete('Xinvalidarg') -endfunc " Keep this last, it messes up highlighting. def Test_substitute_cmd() diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -951,6 +951,9 @@ func_clear_items(ufunc_T *fp) VIM_CLEAR(fp->uf_arg_types); VIM_CLEAR(fp->uf_def_arg_idx); VIM_CLEAR(fp->uf_va_name); + while (fp->uf_type_list.ga_len > 0) + vim_free(((type_T **)fp->uf_type_list.ga_data) + [--fp->uf_type_list.ga_len]); ga_clear(&fp->uf_type_list); #ifdef FEAT_PROFILE VIM_CLEAR(fp->uf_tml_count); @@ -3013,6 +3016,7 @@ ex_function(exarg_T *eap) fp->uf_args = newargs; fp->uf_def_args = default_args; fp->uf_ret_type = &t_any; + fp->uf_func_type = &t_func_any; if (eap->cmdidx == CMD_def) { @@ -3022,7 +3026,7 @@ ex_function(exarg_T *eap) SOURCING_LNUM = sourcing_lnum_top; // parse the argument types - ga_init2(&fp->uf_type_list, sizeof(type_T), 5); + ga_init2(&fp->uf_type_list, sizeof(type_T *), 10); if (argtypes.ga_len > 0) { diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -739,6 +739,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 512, +/**/ 511, /**/ 510, diff --git a/src/vim.h b/src/vim.h --- a/src/vim.h +++ b/src/vim.h @@ -1751,6 +1751,7 @@ void *vim_memset(void *, int, size_t); # define INIT3(a, b, c) # define INIT4(a, b, c, d) # define INIT5(a, b, c, d, e) +# define INIT6(a, b, c, d, e, f) #else # ifndef INIT # define INIT(x) x @@ -1758,6 +1759,7 @@ void *vim_memset(void *, int, size_t); # define INIT3(a, b, c) = {a, b, c} # define INIT4(a, b, c, d) = {a, b, c, d} # define INIT5(a, b, c, d, e) = {a, b, c, d, e} +# define INIT6(a, b, c, d, e, f) = {a, b, c, d, e, f} # define DO_INIT # endif #endif diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -29,8 +29,8 @@ typedef enum { ISN_STORE, // pop into local variable isn_arg.number ISN_STOREV, // pop into v: variable isn_arg.number ISN_STOREG, // pop into global variable isn_arg.string - ISN_STORES, // pop into scirpt variable isn_arg.loadstore - ISN_STORESCRIPT, // pop into scirpt variable isn_arg.script + ISN_STORES, // pop into script variable isn_arg.loadstore + ISN_STORESCRIPT, // pop into script variable isn_arg.script ISN_STOREOPT, // pop into option isn_arg.string ISN_STOREENV, // pop into environment variable isn_arg.string ISN_STOREREG, // pop into register isn_arg.number @@ -191,7 +191,7 @@ typedef struct { // arguments to ISN_LOADS and ISN_STORES typedef struct { - char_u *ls_name; // variable name + char_u *ls_name; // variable name (with s: for ISN_STORES) int ls_sid; // script ID } loadstore_T; diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -120,7 +120,7 @@ struct cctx_S { scope_T *ctx_scope; // current scope, NULL at toplevel garray_T ctx_type_stack; // type of each item on the stack - garray_T *ctx_type_list; // space for adding types + garray_T *ctx_type_list; // list of pointers to allocated types }; static char e_var_notfound[] = N_("E1001: variable not found: %s"); @@ -223,6 +223,26 @@ check_defined(char_u *p, int len, cctx_T return OK; } +/* + * Allocate memory for a type_T and add the pointer to type_gap, so that it can + * be freed later. + */ + static type_T * +alloc_type(garray_T *type_gap) +{ + type_T *type; + + if (ga_grow(type_gap, 1) == FAIL) + return NULL; + type = ALLOC_CLEAR_ONE(type_T); + if (type != NULL) + { + ((type_T **)type_gap->ga_data)[type_gap->ga_len] = type; + ++type_gap->ga_len; + } + return type; +} + static type_T * get_list_type(type_T *member_type, garray_T *type_gap) { @@ -241,10 +261,9 @@ get_list_type(type_T *member_type, garra return &t_list_string; // Not a common type, create a new entry. - if (ga_grow(type_gap, 1) == FAIL) + type = alloc_type(type_gap); + if (type == NULL) return &t_any; - type = ((type_T *)type_gap->ga_data) + type_gap->ga_len; - ++type_gap->ga_len; type->tt_type = VAR_LIST; type->tt_member = member_type; type->tt_argcount = 0; @@ -270,10 +289,9 @@ get_dict_type(type_T *member_type, garra return &t_dict_string; // Not a common type, create a new entry. - if (ga_grow(type_gap, 1) == FAIL) + type = alloc_type(type_gap); + if (type == NULL) return &t_any; - type = ((type_T *)type_gap->ga_data) + type_gap->ga_len; - ++type_gap->ga_len; type->tt_type = VAR_DICT; type->tt_member = member_type; type->tt_argcount = 0; @@ -325,10 +343,9 @@ get_func_type(type_T *ret_type, int argc } // Not a common type or has arguments, create a new entry. - if (ga_grow(type_gap, 1) == FAIL) + type = alloc_type(type_gap); + if (type == NULL) return &t_any; - type = ((type_T *)type_gap->ga_data) + type_gap->ga_len; - ++type_gap->ga_len; type->tt_type = VAR_FUNC; type->tt_member = ret_type; type->tt_args = NULL; @@ -336,6 +353,29 @@ get_func_type(type_T *ret_type, int argc } /* + * For a function type, reserve space for "argcount" argument types. + */ + static int +func_type_add_arg_types( + type_T *functype, + int argcount, + int min_argcount, + garray_T *type_gap) +{ + if (ga_grow(type_gap, 1) == FAIL) + return FAIL; + functype->tt_args = ALLOC_CLEAR_MULT(type_T *, argcount); + if (functype->tt_args == NULL) + return FAIL; + ((type_T **)type_gap->ga_data)[type_gap->ga_len] = (void *)functype->tt_args; + ++type_gap->ga_len; + + functype->tt_argcount = argcount; + functype->tt_min_argcount = min_argcount; + return OK; +} + +/* * Return the type_T for a typval. Only for primitive types. */ static type_T * @@ -810,12 +850,12 @@ generate_PUSHBLOB(cctx_T *cctx, blob_T * * Consumes "name". */ static int -generate_PUSHFUNC(cctx_T *cctx, char_u *name) +generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type) { isn_T *isn; RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_type(cctx, ISN_PUSHFUNC, &t_func_void)) == NULL) + if ((isn = generate_instr_type(cctx, ISN_PUSHFUNC, type)) == NULL) return FAIL; isn->isn_arg.string = name; @@ -1010,7 +1050,7 @@ generate_NEWLIST(cctx_T *cctx, int count // drop the value types stack->ga_len -= count; - // Use the first value type for the list member type. Use "void" for an + // Use the first value type for the list member type. Use "any" for an // empty list. if (count > 0) member = ((type_T **)stack->ga_data)[stack->ga_len]; @@ -1533,22 +1573,27 @@ parse_type(char_u **arg, garray_T *type_ *arg += len; return &t_float; #else - emsg(_("E1055: This Vim is not compiled with float support")); + emsg(_("E1076: This Vim is not compiled with float support")); return &t_any; #endif } if (len == 4 && STRNCMP(*arg, "func", len) == 0) { type_T *type; - type_T *ret_type = &t_void; + type_T *ret_type = &t_any; int argcount = -1; int flags = 0; + int first_optional = -1; type_T *arg_type[MAX_FUNC_ARGS + 1]; // func({type}, ...): {type} *arg += len; if (**arg == '(') { + // "func" may or may not return a value, "func()" does + // not return a value. + ret_type = &t_void; + p = ++*arg; argcount = 0; while (*p != NUL && *p != ')') @@ -1560,6 +1605,18 @@ parse_type(char_u **arg, garray_T *type_ } arg_type[argcount++] = parse_type(&p, type_gap); + if (*p == '?') + { + if (first_optional == -1) + first_optional = argcount; + ++p; + } + else if (first_optional != -1) + { + emsg(_("E1007: mandatory argument after optional argument")); + return &t_any; + } + if (*p != ',' && *skipwhite(p) == ',') { semsg(_(e_no_white_before), ","); @@ -1596,24 +1653,17 @@ parse_type(char_u **arg, garray_T *type_ *arg = skipwhite(*arg); ret_type = parse_type(arg, type_gap); } - type = get_func_type(ret_type, flags == 0 ? argcount : 99, + type = get_func_type(ret_type, + flags == 0 && first_optional == -1 ? argcount : 99, type_gap); if (flags != 0) type->tt_flags = flags; if (argcount > 0) { - int type_ptr_cnt = (sizeof(type_T *) * argcount - + sizeof(type_T) - 1) / sizeof(type_T); - - type->tt_argcount = argcount; - // Get space from "type_gap" to avoid having to keep track - // of the pointer and freeing it. - ga_grow(type_gap, type_ptr_cnt); - if (ga_grow(type_gap, type_ptr_cnt) == FAIL) + if (func_type_add_arg_types(type, argcount, + first_optional == -1 ? argcount : first_optional, + type_gap) == FAIL) return &t_any; - type->tt_args = - ((type_T **)type_gap->ga_data) + type_gap->ga_len; - type_gap->ga_len += type_ptr_cnt; mch_memmove(type->tt_args, arg_type, sizeof(type_T *) * argcount); } @@ -1775,7 +1825,62 @@ type_name(type_T *type, char **tofree) return *tofree; } } - // TODO: function and partial argument types + if (type->tt_type == VAR_FUNC || type->tt_type == VAR_PARTIAL) + { + garray_T ga; + int i; + + ga_init2(&ga, 1, 100); + if (ga_grow(&ga, 20) == FAIL) + return "[unknown]"; + *tofree = ga.ga_data; + STRCPY(ga.ga_data, "func("); + ga.ga_len += 5; + + for (i = 0; i < type->tt_argcount; ++i) + { + char *arg_free; + char *arg_type = type_name(type->tt_args[i], &arg_free); + int len; + + if (i > 0) + { + STRCPY(ga.ga_data + ga.ga_len, ", "); + ga.ga_len += 2; + } + len = (int)STRLEN(arg_type); + if (ga_grow(&ga, len + 6) == FAIL) + { + vim_free(arg_free); + return "[unknown]"; + } + *tofree = ga.ga_data; + STRCPY(ga.ga_data + ga.ga_len, arg_type); + ga.ga_len += len; + vim_free(arg_free); + } + + if (type->tt_member == &t_void) + STRCPY(ga.ga_data + ga.ga_len, ")"); + else + { + char *ret_free; + char *ret_name = type_name(type->tt_member, &ret_free); + int len; + + len = (int)STRLEN(ret_name) + 4; + if (ga_grow(&ga, len) == FAIL) + { + vim_free(ret_free); + return "[unknown]"; + } + *tofree = ga.ga_data; + STRCPY(ga.ga_data + ga.ga_len, "): "); + STRCPY(ga.ga_data + ga.ga_len + 3, ret_name); + vim_free(ret_free); + } + return ga.ga_data; + } return name; } @@ -1868,7 +1973,9 @@ free_imported(cctx_T *cctx) } /* - * Generate an instruction to load script-local variable "name". + * Generate an instruction to load script-local variable "name", without the + * leading "s:". + * Also finds imported variables. */ static int compile_load_scriptvar( @@ -1933,7 +2040,7 @@ compile_load_scriptvar( } else { - // TODO: check this is a variable, not a function + // TODO: check this is a variable, not a function? generate_VIM9SCRIPT(cctx, ISN_LOADSCRIPT, import->imp_sid, import->imp_var_vals_idx, @@ -1947,6 +2054,17 @@ compile_load_scriptvar( return FAIL; } + static int +generate_funcref(cctx_T *cctx, char_u *name) +{ + ufunc_T *ufunc = find_func(name, cctx); + + if (ufunc == NULL) + return FAIL; + + return generate_PUSHFUNC(cctx, vim_strsave(name), ufunc->uf_func_type); +} + /* * Compile a variable name into a load instruction. * "end" points to just after the name. @@ -2051,10 +2169,21 @@ compile_load(char_u **arg, char_u *end_a || (len == 5 && STRNCMP("false", *arg, 5) == 0)) res = generate_PUSHBOOL(cctx, **arg == 't' ? VVAL_TRUE : VVAL_FALSE); - else if (SCRIPT_ITEM(current_sctx.sc_sid)->sn_version - == SCRIPT_VERSION_VIM9) - // in Vim9 script "var" can be script-local. - res = compile_load_scriptvar(cctx, name, *arg, &end, error); + else + { + // "var" can be script-local even without using "s:" if it + // already exists. + if (SCRIPT_ITEM(current_sctx.sc_sid)->sn_version + == SCRIPT_VERSION_VIM9 + || lookup_script(*arg, len) == OK) + res = compile_load_scriptvar(cctx, name, *arg, &end, + FALSE); + + // When the name starts with an uppercase letter or "x:" it + // can be a user defined function. + if (res == FAIL && (ASCII_ISUPPER(*name) || name[1] == ':')) + res = generate_funcref(cctx, name); + } } } if (gen_load) @@ -2149,11 +2278,10 @@ compile_call(char_u **arg, size_t varlen // builtin function idx = find_internal_func(name); if (idx >= 0) - { res = generate_BCALL(cctx, idx, argcount); - goto theend; - } - semsg(_(e_unknownfunc), namebuf); + else + semsg(_(e_unknownfunc), namebuf); + goto theend; } // If we can find the function by name generate the right call. @@ -2264,6 +2392,8 @@ type_mismatch(type_T *expected, type_T * static int check_type(type_T *expected, type_T *actual, int give_msg) { + int ret = OK; + if (expected->tt_type != VAR_UNKNOWN) { if (expected->tt_type != actual->tt_type) @@ -2274,17 +2404,21 @@ check_type(type_T *expected, type_T *act } if (expected->tt_type == VAR_DICT || expected->tt_type == VAR_LIST) { - int ret; - // void is used for an empty list or dict - if (actual->tt_member == &t_void) - ret = OK; - else + if (actual->tt_member != &t_void) + ret = check_type(expected->tt_member, actual->tt_member, FALSE); + } + else if (expected->tt_type == VAR_FUNC) + { + if (expected->tt_member != &t_any) ret = check_type(expected->tt_member, actual->tt_member, FALSE); - if (ret == FAIL && give_msg) - type_mismatch(expected, actual); - return ret; + if (ret == OK && expected->tt_argcount != -1 + && (actual->tt_argcount < expected->tt_min_argcount + || actual->tt_argcount > expected->tt_argcount)) + ret = FAIL; } + if (ret == FAIL && give_msg) + type_mismatch(expected, actual); } return OK; } @@ -2357,6 +2491,7 @@ compile_lambda(char_u **arg, cctx_T *cct 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. @@ -2401,6 +2536,7 @@ compile_lambda_call(char_u **arg, cctx_T 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. @@ -3679,7 +3815,8 @@ compile_assignment(char_u *arg, exarg_T type = &t_string; if (is_decl) { - semsg(_("E1065: Cannot declare an environment variable: %s"), name); + semsg(_("E1065: Cannot declare an environment variable: %s"), + name); goto theend; } } @@ -3761,7 +3898,7 @@ compile_assignment(char_u *arg, exarg_T if (is_decl) { semsg(_("E1054: Variable already declared in the script: %s"), - name); + name); goto theend; } } @@ -3803,17 +3940,20 @@ compile_assignment(char_u *arg, exarg_T goto theend; } - // +=, /=, etc. require an existing variable if (idx < 0 && dest == dest_local && cctx->ctx_skip != TRUE) { if (oplen > 1 && !heredoc) { + // +=, /=, etc. require an existing variable semsg(_("E1020: cannot use an operator on a new variable: %s"), name); goto theend; } // new local variable + if ((type->tt_type == VAR_FUNC || type->tt_type == VAR_PARTIAL) + && var_check_func_name(name, TRUE)) + goto theend; idx = reserve_local(cctx, arg, varlen, cmdidx == CMD_const, type); if (idx < 0) goto theend; @@ -3897,7 +4037,7 @@ compile_assignment(char_u *arg, exarg_T if (idx >= 0 && (is_decl || !has_type)) { lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; - if (!has_type) + if (new_local && !has_type) { if (stacktype->tt_type == VAR_VOID) { @@ -3905,11 +4045,19 @@ compile_assignment(char_u *arg, exarg_T goto theend; } else - lvar->lv_type = stacktype; + { + // An empty list or dict has a &t_void member, for a + // variable that implies &t_any. + if (stacktype == &t_list_empty) + lvar->lv_type = &t_list_any; + else if (stacktype == &t_dict_empty) + lvar->lv_type = &t_dict_any; + else + lvar->lv_type = stacktype; + } } - else - if (check_type(lvar->lv_type, stacktype, TRUE) == FAIL) - goto theend; + else if (check_type(lvar->lv_type, stacktype, TRUE) == FAIL) + goto theend; } else if (*p != '=' && check_type(type, stacktype, TRUE) == FAIL) goto theend; @@ -3946,7 +4094,7 @@ compile_assignment(char_u *arg, exarg_T generate_PUSHBLOB(cctx, NULL); break; case VAR_FUNC: - generate_PUSHFUNC(cctx, NULL); + generate_PUSHFUNC(cctx, NULL, &t_func_void); break; case VAR_PARTIAL: generate_PUSHPARTIAL(cctx, NULL); @@ -4039,7 +4187,24 @@ compile_assignment(char_u *arg, exarg_T idx = get_script_item_idx(sid, rawname, TRUE); // TODO: specific type if (idx < 0) - generate_OLDSCRIPT(cctx, ISN_STORES, name, sid, &t_any); + { + char_u *name_s = name; + + // Include s: in the name for store_var() + if (name[1] != ':') + { + int len = (int)STRLEN(name) + 3; + + name_s = alloc(len); + if (name_s == NULL) + name_s = name; + else + vim_snprintf((char *)name_s, len, "s:%s", name); + } + generate_OLDSCRIPT(cctx, ISN_STORES, name_s, sid, &t_any); + if (name_s != name) + vim_free(name_s); + } else generate_VIM9SCRIPT(cctx, ISN_STORESCRIPT, sid, idx, &t_any); @@ -5357,13 +5522,17 @@ compile_def_function(ufunc_T *ufunc, int { int is_ex_command; + // Bail out on the first error to avoid a flood of errors and report + // the right line number when inside try/catch. + if (emsg_before != called_emsg) + goto erret; + if (line != NULL && *line == '|') // the line continues after a '|' ++line; else if (line != NULL && *line != NUL) { - if (emsg_before == called_emsg) - semsg(_("E488: Trailing characters: %s"), line); + semsg(_("E488: Trailing characters: %s"), line); goto erret; } else @@ -5653,6 +5822,37 @@ compile_def_function(ufunc_T *ufunc, int dfunc->df_varcount = cctx.ctx_max_local; } + { + int argcount = ufunc->uf_args.ga_len + + (ufunc->uf_va_name == NULL ? 0 : 1); + + // Create a type for the function, with the return type and any + // argument types. + ufunc->uf_func_type = get_func_type(ufunc->uf_ret_type, argcount, + &ufunc->uf_type_list); + if (argcount > 0) + { + if (func_type_add_arg_types(ufunc->uf_func_type, argcount, + argcount - ufunc->uf_def_args.ga_len, + &ufunc->uf_type_list) == FAIL) + { + ret = FAIL; + goto erret; + } + if (ufunc->uf_arg_types == NULL) + { + int i; + + // lambda does not have argument types. + for (i = 0; i < argcount; ++i) + ufunc->uf_func_type->tt_args[i] = &t_any; + } + else + mch_memmove(ufunc->uf_func_type->tt_args, + ufunc->uf_arg_types, sizeof(type_T *) * argcount); + } + } + ret = OK; erret: diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -377,7 +377,7 @@ store_var(char_u *name, typval_T *tv) funccal_entry_T entry; save_funccal(&entry); - set_var_const(name, NULL, tv, FALSE, 0); + set_var_const(name, NULL, tv, FALSE, LET_NO_COMMAND); restore_funccal(); } @@ -739,7 +739,7 @@ call_def_function( --ectx.ec_stack.ga_len; if (di == NULL) - store_var(iptr->isn_arg.string, STACK_TV_BOT(0)); + store_var(name, STACK_TV_BOT(0)); else { clear_tv(&di->di_tv); @@ -1828,7 +1828,7 @@ ex_disassemble(exarg_T *eap) iptr->isn_arg.loadstore.ls_sid); smsg("%4d LOADS s:%s from %s", current, - iptr->isn_arg.string, si->sn_name); + iptr->isn_arg.loadstore.ls_name, si->sn_name); } break; case ISN_LOADG: @@ -1865,7 +1865,7 @@ ex_disassemble(exarg_T *eap) iptr->isn_arg.loadstore.ls_sid); smsg("%4d STORES %s in %s", current, - iptr->isn_arg.string, si->sn_name); + iptr->isn_arg.loadstore.ls_name, si->sn_name); } break; case ISN_STORESCRIPT: